diff --git a/app/assets/javascripts/repository/components/commit_info.vue b/app/assets/javascripts/repository/components/commit_info.vue
index f9fb971b2968fcc056cc727071bbd692a1106f2e..64578570224ab9ab80e7312c1531ce7af985983d 100644
--- a/app/assets/javascripts/repository/components/commit_info.vue
+++ b/app/assets/javascripts/repository/components/commit_info.vue
@@ -146,13 +146,37 @@ export default {
+
- {{ commit.committerName }}
+
+ {{ commit.committer.name }}
+
+
+ {{ commit.committerName }}
+
+
+ {{ commit.committerName }}
+
diff --git a/app/graphql/queries/repository/path_last_commit.query.graphql b/app/graphql/queries/repository/path_last_commit.query.graphql
index d90ce5c16693a44ff904fe1a3fe2bb325d6ea956..d69b15f212508f6474cc44509ebead6f650d1318 100644
--- a/app/graphql/queries/repository/path_last_commit.query.graphql
+++ b/app/graphql/queries/repository/path_last_commit.query.graphql
@@ -15,6 +15,8 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!, $refType:
webPath
committerName
committerEmail
+ committerAvatarUrl
+ committerWebUrl
committedDate
authoredDate
authorName
@@ -27,6 +29,13 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!, $refType:
avatarUrl
webPath
}
+ committer {
+ __typename
+ id
+ name
+ avatarUrl
+ webPath
+ }
signature {
__typename
... on GpgSignature {
diff --git a/spec/frontend/repository/components/commit_info_spec.js b/spec/frontend/repository/components/commit_info_spec.js
index 1a451030f65ca5e1e71df06f6bbf8b5145818387..0f5fb368353a7ca1bfdefe4cf9a48e47a38b0bd2 100644
--- a/spec/frontend/repository/components/commit_info_spec.js
+++ b/spec/frontend/repository/components/commit_info_spec.js
@@ -160,5 +160,134 @@ describe('Repository last commit component', () => {
commitMockWithDifferentCommitter.committedDate,
);
});
+
+ describe('when committer is a GitLab user', () => {
+ const commitMockWithCommitterUser = {
+ author: { name: 'John Doe', email: 'john@example.com' },
+ committerName: 'Jane Smith',
+ committerEmail: 'jane@example.com',
+ committedDate: '2019-02-01',
+ committer: {
+ name: 'Jane Smith',
+ avatarUrl: 'https://gitlab.com/jane-avatar.jpg',
+ webPath: '/jane-smith',
+ },
+ };
+
+ beforeEach(() => createComponent({ commitMock: commitMockWithCommitterUser }));
+
+ it('displays clickable committer avatar using UserAvatarLink', () => {
+ const avatarLinks = wrapper.findAllComponents(UserAvatarLink);
+ const committerAvatar = avatarLinks.at(1); // First is author, second is committer
+
+ expect(committerAvatar.exists()).toBe(true);
+ expect(committerAvatar.props()).toMatchObject({
+ linkHref: '/jane-smith',
+ imgSrc: 'https://gitlab.com/jane-avatar.jpg',
+ imgAlt: 'Jane Smith',
+ imgSize: 16,
+ });
+ });
+
+ it('displays committer name as a clickable link', () => {
+ const committerLink = wrapper.find('.commit-committer-link');
+
+ expect(committerLink.exists()).toBe(true);
+ expect(committerLink.attributes('href')).toBe(
+ commitMockWithCommitterUser.committer.webPath,
+ );
+ expect(committerLink.text()).toBe('Jane Smith');
+ });
+ });
+
+ describe('when committer is not a GitLab user but has avatar URL', () => {
+ const commitMockWithNonUserCommitter = {
+ author: { name: 'John Doe', email: 'john@example.com' },
+ committerName: 'Jane Smith',
+ committerEmail: 'jane@example.com',
+ committerAvatarUrl: 'https://gravatar.com/avatar/123',
+ committerWebUrl: null,
+ committedDate: '2019-02-01',
+ committer: null,
+ };
+
+ beforeEach(() => createComponent({ commitMock: commitMockWithNonUserCommitter }));
+
+ it('displays committer avatar without link using UserAvatarImage', () => {
+ const avatarImages = findUserAvatarImages();
+ const committerAvatar = avatarImages.at(0);
+
+ expect(committerAvatar.exists()).toBe(true);
+ expect(committerAvatar.props()).toMatchObject({
+ size: 16,
+ imgSrc: 'https://gravatar.com/avatar/123',
+ });
+ });
+
+ it('displays committer name as plain text without link', () => {
+ const committerLink = wrapper.find('.commit-committer-link');
+ const text = findCommitterWrapper().text();
+
+ expect(committerLink.exists()).toBe(false);
+ expect(text).toContain('Jane Smith');
+ });
+ });
+
+ describe('when committer has web URL but no user object', () => {
+ const commitMockWithWebUrl = {
+ author: { name: 'John Doe', email: 'john@example.com' },
+ committerName: 'Jane Smith',
+ committerEmail: 'jane@example.com',
+ committerAvatarUrl: 'https://gravatar.com/avatar/123',
+ committerWebUrl: '/jane-smith',
+ committedDate: '2019-02-01',
+ committer: null,
+ };
+
+ beforeEach(() => createComponent({ commitMock: commitMockWithWebUrl }));
+
+ it('displays committer avatar without UserAvatarLink', () => {
+ const avatarImages = findUserAvatarImages();
+
+ expect(avatarImages.at(0).exists()).toBe(true);
+ expect(avatarImages.at(0).props('imgSrc')).toBe('https://gravatar.com/avatar/123');
+ });
+
+ it('displays committer name as a clickable link', () => {
+ const committerLink = wrapper.find('.commit-committer-link');
+
+ expect(committerLink.exists()).toBe(true);
+ expect(committerLink.attributes('href')).toBe('/jane-smith');
+ expect(committerLink.text()).toBe('Jane Smith');
+ });
+ });
+
+ describe('when committer has no avatar or profile URL', () => {
+ const commitMockMinimalCommitter = {
+ author: { name: 'John Doe', email: 'john@example.com' },
+ committerName: 'Jane Smith',
+ committerEmail: 'jane@example.com',
+ committerAvatarUrl: null,
+ committerWebUrl: null,
+ committedDate: '2019-02-01',
+ committer: null,
+ };
+
+ beforeEach(() => createComponent({ commitMock: commitMockMinimalCommitter }));
+
+ it('does not display committer avatar', () => {
+ const avatarImages = findUserAvatarImages();
+
+ expect(avatarImages).toHaveLength(0);
+ });
+
+ it('displays committer name as plain text', () => {
+ const committerLink = wrapper.find('.commit-committer-link');
+ const text = findCommitterWrapper().text();
+
+ expect(committerLink.exists()).toBe(false);
+ expect(text).toContain('Jane Smith');
+ });
+ });
});
});
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index dc181d86b831dadc1d103b78af5e8531c45a7ff4..a05d72bfcbd5d9c714b80aaf146791ca61b12ee8 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -70,7 +70,7 @@ describe('Repository last commit component', () => {
await waitForPromises();
- const commit = { ...commitData.project?.repository.paginatedTree.nodes[0].lastCommit };
+ const commit = { ...commitData.data.project?.repository.lastCommit };
expect(findCommitInfo().props().commit).toMatchObject(commit);
});
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index 23c730d8c75789a9f60362b0e53eca766d78e65b..de534f955433370cad23817a612222c65fcca002 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -340,6 +340,8 @@ export const createCommitData = ({ pipelineEdges = defaultPipelineEdges, signatu
webPath: '/commit/123',
committerName: 'Test Committer',
committerEmail: 'testcommitter@example.com',
+ committerAvatarUrl: 'https://test.com/committer-avatar',
+ committerWebUrl: 'https://test.com/committer',
committedDate: '2019-02-02',
authoredDate: '2019-01-01',
authorName: 'Test',
@@ -352,6 +354,13 @@ export const createCommitData = ({ pipelineEdges = defaultPipelineEdges, signatu
avatarUrl: 'https://test.com',
webPath: '/test',
},
+ committer: {
+ __typename: 'UserCore',
+ id: 'gid://gitlab/User/2',
+ name: 'Test Committer',
+ avatarUrl: 'https://test.com/committer-avatar',
+ webPath: '/committer',
+ },
signature,
pipelines: {
__typename: 'PipelineConnection',