diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 35cd44f224fb2d350a344f4cf58944387e6601a8..b14716f618525b63e4d58e7762bce1ccaabdb268 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -31,6 +31,16 @@ class GroupType < NamespaceType
null: false,
description: 'Path for editing group.'
+ field :admin_edit_path, GraphQL::Types::String,
+ null: true,
+ description: 'Admin path for editing group. Only available to admins.',
+ authorize: :admin_all_resources
+
+ field :admin_show_path, GraphQL::Types::String,
+ null: true,
+ description: 'Admin path of the group. Only available to admins.',
+ authorize: :admin_all_resources
+
field :avatar_url,
type: GraphQL::Types::String,
null: true,
@@ -532,6 +542,14 @@ def edit_path
::Gitlab::Routing.url_helpers.edit_group_path(group)
end
+ def admin_show_path
+ ::Gitlab::Routing.url_helpers.admin_group_path(group)
+ end
+
+ def admin_edit_path
+ ::Gitlab::Routing.url_helpers.admin_group_edit_path(group)
+ end
+
private
def group
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index af10eb05df709b55eb6a578fd9279b209ea8dd7f..a42cee6f5e798e63f9733ffe88dfa7273068f471 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -136,6 +136,16 @@ def self.authorization_scopes
null: false,
description: 'Path for editing project.'
+ field :admin_edit_path, GraphQL::Types::String,
+ null: true,
+ description: 'Admin path for editing project. Only available to admins.',
+ authorize: :admin_all_resources
+
+ field :admin_show_path, GraphQL::Types::String,
+ null: true,
+ description: 'Admin path of the project. Only available to admins.',
+ authorize: :admin_all_resources
+
field :forks_count, GraphQL::Types::Int,
null: false,
calls_gitaly: true, # 4 times
@@ -1143,6 +1153,18 @@ def edit_path
::Gitlab::Routing.url_helpers.edit_project_path(project)
end
+ def admin_show_path
+ ::Gitlab::Routing.url_helpers.admin_namespace_project_path(
+ { id: project.to_param, namespace_id: project.namespace.to_param }
+ )
+ end
+
+ def admin_edit_path
+ ::Gitlab::Routing.url_helpers.edit_admin_namespace_project_path(
+ { id: project.to_param, namespace_id: project.namespace.to_param }
+ )
+ end
+
def grafana_integration
nil
end
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index 1fcc7e4f11d1ce3f564aa2860b13bb8dc78f5f39..f6fc331e9c6c4938e0e2f2e524ac86bc66fd9c57 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -31563,6 +31563,8 @@ GPG signature for a signed commit.
| `actualRepositorySizeLimit` | [`Float`](#float) | Size limit for repositories in the namespace in bytes. This limit only applies to namespaces under Project limit enforcement. |
| `actualSizeLimit` | [`Float`](#float) | The actual storage size limit (in bytes) based on the enforcement type of either repository or namespace. This limit is agnostic of enforcement type. |
| `additionalPurchasedStorageSize` | [`Float`](#float) | Additional storage purchased for the root namespace in bytes. |
+| `adminEditPath` | [`String`](#string) | Admin path for editing group. Only available to admins. |
+| `adminShowPath` | [`String`](#string) | Admin path of the group. Only available to admins. |
| `aiUsageData` {{< icon name="warning-solid" >}} | [`AiUsageData`](#aiusagedata) | **Introduced** in GitLab 17.5. **Status**: Experiment. AI-related data. |
| `allowStaleRunnerPruning` | [`Boolean!`](#boolean) | Indicates whether to regularly prune stale group runners. Defaults to false. |
| `amazonS3Configurations` | [`AmazonS3ConfigurationTypeConnection`](#amazons3configurationtypeconnection) | Amazon S3 configurations that receive audit events belonging to the group. (see [Connections](#connections)) |
@@ -39986,6 +39988,8 @@ Project-level settings for product analytics provider.
| Name | Type | Description |
| ---- | ---- | ----------- |
| `actualRepositorySizeLimit` | [`Float`](#float) | Size limit for the repository in bytes. |
+| `adminEditPath` | [`String`](#string) | Admin path for editing project. Only available to admins. |
+| `adminShowPath` | [`String`](#string) | Admin path of the project. Only available to admins. |
| `agentConfigurations` | [`AgentConfigurationConnection`](#agentconfigurationconnection) | Agent configurations defined by the project. (see [Connections](#connections)) |
| `aiAgents` {{< icon name="warning-solid" >}} | [`AiAgentConnection`](#aiagentconnection) | **Introduced** in GitLab 16.9. **Status**: Experiment. Ai Agents for the project. |
| `aiUsageData` {{< icon name="warning-solid" >}} | [`AiUsageData`](#aiusagedata) | **Introduced** in GitLab 17.5. **Status**: Experiment. AI-related data. |
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index 22a81a1a1bcbb860b50b7dfa25375cb192ef6e5a..73fa8a91b2ce5b8cf515066e6c0e6269c4483fe2 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -20,7 +20,8 @@
expected_fields = %w[
id name path full_name full_path description description_html visibility archived
is_self_archived lfs_enabled request_access_enabled projects root_storage_statistics
- web_url web_path edit_path avatar_url share_with_group_lock project_creation_level
+ web_url web_path edit_path admin_show_path admin_edit_path avatar_url
+ share_with_group_lock project_creation_level
descendant_groups_count group_members_count projects_count
subgroup_creation_level require_two_factor_authentication
two_factor_grace_period auto_devops_enabled emails_disabled emails_enabled
@@ -562,4 +563,78 @@ def clean_state_query
expect(edit_path).to eq("/groups/#{group.full_path}/-/edit")
end
end
+
+ describe 'admin_show_path' do
+ let_it_be(:group) { create(:group, :public) }
+
+ let(:query) do
+ %(
+ query {
+ group(fullPath: "#{group.full_path}") {
+ adminShowPath
+ }
+ }
+ )
+ end
+
+ subject(:admin_show_path) do
+ GitlabSchema
+ .execute(query, context: { current_user: current_user })
+ .as_json
+ .dig('data', 'group', 'adminShowPath')
+ end
+
+ context 'when current user is an admin', :enable_admin_mode do
+ let_it_be(:current_user) { create(:user, :admin) }
+
+ it 'returns admin_show_path field' do
+ expect(admin_show_path).to eq("/admin/groups/#{group.full_path}")
+ end
+ end
+
+ context 'when current user is not an admin' do
+ let_it_be(:current_user) { create(:user) }
+
+ it 'returns admin_show_path field as null' do
+ expect(admin_show_path).to be_nil
+ end
+ end
+ end
+
+ describe 'admin_edit_path' do
+ let_it_be(:group) { create(:group, :public) }
+
+ let(:query) do
+ %(
+ query {
+ group(fullPath: "#{group.full_path}") {
+ adminEditPath
+ }
+ }
+ )
+ end
+
+ subject(:admin_edit_path) do
+ GitlabSchema
+ .execute(query, context: { current_user: current_user })
+ .as_json
+ .dig('data', 'group', 'adminEditPath')
+ end
+
+ context 'when current user is an admin', :enable_admin_mode do
+ let_it_be(:current_user) { create(:user, :admin) }
+
+ it 'returns admin_edit_path field' do
+ expect(admin_edit_path).to eq("/admin/groups/#{group.full_path}/edit")
+ end
+ end
+
+ context 'when current user is not an admin' do
+ let_it_be(:current_user) { create(:user) }
+
+ it 'returns admin_edit_path field as null' do
+ expect(admin_edit_path).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 7ae8c477c0a787439038daf87deecb599f241718..b729365a205796028ea87935afadc74e133e01b5 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -21,7 +21,7 @@
expected_fields = %w[
user_permissions id full_path path name_with_namespace
name description description_html tag_list topics ssh_url_to_repo
- http_url_to_repo web_url web_path edit_path star_count forks_count
+ http_url_to_repo web_url web_path edit_path admin_show_path admin_edit_path star_count forks_count
created_at updated_at last_activity_at archived is_self_archived visibility
container_registry_enabled shared_runners_enabled
lfs_enabled merge_requests_ff_only_enabled avatar_url
@@ -1432,6 +1432,80 @@
end
end
+ describe 'admin_show_path' do
+ let_it_be(:project) { create(:project, :public) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ adminShowPath
+ }
+ }
+ )
+ end
+
+ subject(:admin_show_path) do
+ GitlabSchema
+ .execute(query, context: { current_user: current_user })
+ .as_json
+ .dig('data', 'project', 'adminShowPath')
+ end
+
+ context 'when current user is an admin', :enable_admin_mode do
+ let_it_be(:current_user) { create(:user, :admin) }
+
+ it 'returns admin_show_path field' do
+ expect(admin_show_path).to eq("/admin/projects/#{project.full_path}")
+ end
+ end
+
+ context 'when current user is not an admin' do
+ let_it_be(:current_user) { create(:user) }
+
+ it 'returns admin_show_path field as null' do
+ expect(admin_show_path).to be_nil
+ end
+ end
+ end
+
+ describe 'admin_edit_path' do
+ let_it_be(:project) { create(:project, :public) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ adminEditPath
+ }
+ }
+ )
+ end
+
+ subject(:admin_edit_path) do
+ GitlabSchema
+ .execute(query, context: { current_user: current_user })
+ .as_json
+ .dig('data', 'project', 'adminEditPath')
+ end
+
+ context 'when current user is an admin', :enable_admin_mode do
+ let_it_be(:current_user) { create(:user, :admin) }
+
+ it 'returns admin_edit_path field' do
+ expect(admin_edit_path).to eq("/admin/projects/#{project.full_path}/edit")
+ end
+ end
+
+ context 'when current user is not an admin' do
+ let_it_be(:current_user) { create(:user) }
+
+ it 'returns admin_edit_path field as null' do
+ expect(admin_edit_path).to be_nil
+ end
+ end
+ end
+
describe 'organization_edit_path' do
let_it_be(:user) { create(:user) }
let_it_be(:organization) { create(:organization) }