diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb index 9fadb44220d7ca327ced9ce8a4f7fcaf0df36ad7..e943f987499f7f40ca4ae6f3b4605362dc77ad63 100644 --- a/app/graphql/types/tree/tree_type.rb +++ b/app/graphql/types/tree/tree_type.rb @@ -6,6 +6,8 @@ module Tree class TreeType < BaseObject graphql_name 'Tree' + present_using ::Projects::TreePresenter + # Complexity 10 as it triggers a Gitaly call on each render field :last_commit, Types::Repositories::CommitType, null: true, complexity: 10, calls_gitaly: true, resolver: Resolvers::LastCommitResolver, @@ -22,6 +24,11 @@ class TreeType < BaseObject description: 'Blobs of the tree.', calls_gitaly: true + field :permalink_path, GraphQL::Types::String, null: true, + description: 'Web path to tree permalink.', + calls_gitaly: true, + experiment: { milestone: '17.11' } + def trees Gitlab::Graphql::Representation::TreeEntry.decorate(object.trees, object.repository) end diff --git a/app/presenters/projects/tree_presenter.rb b/app/presenters/projects/tree_presenter.rb new file mode 100644 index 0000000000000000000000000000000000000000..51ce5b5d4e1432504eb811095d88fb58645e537c --- /dev/null +++ b/app/presenters/projects/tree_presenter.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Projects + class TreePresenter < Gitlab::View::Presenter::Delegated + presents Tree, as: :tree + + def permalink_path + return unless tree.sha.present? + + project = tree.repository.project + commit = tree.repository.commit(tree.sha) + return unless commit + + path = tree.path.presence + full_path = path.present? ? File.join(commit.sha, path) : commit.sha + + Gitlab::Routing.url_helpers.project_tree_path(project, full_path) + end + end +end diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 37bcae456589b08dd5afe6a171d986a8c032c1f0..495bc7343e3b05447775905fb7e83991a7217b78 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -38829,6 +38829,7 @@ Representing a to-do entry. | Name | Type | Description | | ---- | ---- | ----------- | | `blobs` | [`BlobConnection!`](#blobconnection) | Blobs of the tree. (see [Connections](#connections)) | +| `permalinkPath` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.11. **Status**: Experiment. Web path to tree permalink. | | `submodules` | [`SubmoduleConnection!`](#submoduleconnection) | Sub-modules of the tree. (see [Connections](#connections)) | | `trees` | [`TreeEntryConnection!`](#treeentryconnection) | Trees of the tree. (see [Connections](#connections)) | diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb index 362ecdfca91ffcb70f5dc5d26321064c42739573..573b69047421ede5c7fb00655f8c8e589929c9f1 100644 --- a/spec/graphql/types/tree/tree_type_spec.rb +++ b/spec/graphql/types/tree/tree_type_spec.rb @@ -5,5 +5,5 @@ RSpec.describe Types::Tree::TreeType do specify { expect(described_class.graphql_name).to eq('Tree') } - specify { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) } + specify { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit, :permalink_path) } end diff --git a/spec/presenters/projects/tree_presenter_spec.rb b/spec/presenters/projects/tree_presenter_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..72bd89015e6cdf5bf0cca9a017e7d3da9cd5a730 --- /dev/null +++ b/spec/presenters/projects/tree_presenter_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::TreePresenter, feature_category: :source_code_management do + let_it_be(:project) { create(:project, :repository) } # rubocop:disable RSpec/FactoryBot/AvoidCreate -- Need persisted objects + let(:repository) { project.repository } + let(:user) { project.first_owner } + + let(:ref) { 'HEAD' } + let(:path) { 'lib' } + + let(:commit) { repository.commit(ref) } + let(:tree) { repository.tree(ref, path) } + + subject(:presenter) { described_class.new(tree, current_user: user) } + + describe '#permalink_path' do + it 'returns the permalink path with commit SHA and directory path' do + expect(presenter.permalink_path).to eq("/#{project.full_path}/-/tree/#{commit.sha}/#{path}") + end + + context 'when tree path is empty (root tree)' do + let(:path) { '' } + + it 'returns the permalink path pointing to the commit SHA only' do + expect(presenter.permalink_path).to eq("/#{project.full_path}/-/tree/#{commit.sha}/") + end + end + + context 'when tree has no sha' do + before do + tree.sha = nil + end + + it 'returns nil' do + expect(presenter.permalink_path).to be_nil + end + end + + context 'when commit is not found' do + before do + allow(repository).to receive(:commit).and_return(nil) + end + + let(:tree) do + repository.tree(ref, path).tap do |t| + t.sha = 'nonexistentsha123' + end + end + + it 'returns nil' do + expect(presenter.permalink_path).to be_nil + end + end + end +end