Skip to content
Snippets Groups Projects
Commit 99caf319 authored by Patrick Bajao's avatar Patrick Bajao :red_circle:
Browse files

Implement diffLlmSummaries property for MergeRequestType

This adds a `diffLlmSummaries` property to MergeRequestType in
GraphQL API.

It returns the following properties:
- `merge_request_diff_id`
- `user`
- `provider`
- `content`
- `created_at`
- `updated_at`

Changelog: added
EE: true
parent cecd7c23
No related branches found
No related tags found
1 merge request!121656Implement diffLlmSummaries property for MergeRequestType
Showing with 199 additions and 2 deletions
......@@ -9677,6 +9677,29 @@ The connection type for [`MergeRequest`](#mergerequest).
| <a id="mergerequestconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
| <a id="mergerequestconnectiontotaltimetomerge"></a>`totalTimeToMerge` | [`Float`](#float) | Total sum of time to merge, in seconds, for the collection of merge requests. |
 
#### `MergeRequestDiffLlmSummaryConnection`
The connection type for [`MergeRequestDiffLlmSummary`](#mergerequestdiffllmsummary).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestdiffllmsummaryconnectionedges"></a>`edges` | [`[MergeRequestDiffLlmSummaryEdge]`](#mergerequestdiffllmsummaryedge) | A list of edges. |
| <a id="mergerequestdiffllmsummaryconnectionnodes"></a>`nodes` | [`[MergeRequestDiffLlmSummary]`](#mergerequestdiffllmsummary) | A list of nodes. |
| <a id="mergerequestdiffllmsummaryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `MergeRequestDiffLlmSummaryEdge`
The edge type for [`MergeRequestDiffLlmSummary`](#mergerequestdiffllmsummary).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestdiffllmsummaryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="mergerequestdiffllmsummaryedgenode"></a>`node` | [`MergeRequestDiffLlmSummary`](#mergerequestdiffllmsummary) | The item at the end of the edge. |
#### `MergeRequestDiffRegistryConnection`
 
The connection type for [`MergeRequestDiffRegistry`](#mergerequestdiffregistry).
......@@ -17114,6 +17137,7 @@ Defines which user roles, users, or groups can merge into a protected branch.
| <a id="mergerequestdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
| <a id="mergerequestdetailedmergestatus"></a>`detailedMergeStatus` | [`DetailedMergeStatus`](#detailedmergestatus) | Detailed merge status of the merge request. |
| <a id="mergerequestdiffheadsha"></a>`diffHeadSha` | [`String`](#string) | Diff head SHA of the merge request. |
| <a id="mergerequestdiffllmsummaries"></a>`diffLlmSummaries` **{warning-solid}** | [`MergeRequestDiffLlmSummaryConnection`](#mergerequestdiffllmsummaryconnection) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Diff summaries generated by AI. |
| <a id="mergerequestdiffrefs"></a>`diffRefs` | [`DiffRefs`](#diffrefs) | References of the base SHA, the head SHA, and the start SHA for this merge request. |
| <a id="mergerequestdiffstatssummary"></a>`diffStatsSummary` | [`DiffStatsSummary`](#diffstatssummary) | Summary of which files were changed in this merge request. |
| <a id="mergerequestdiscussionlocked"></a>`discussionLocked` | [`Boolean!`](#boolean) | Indicates if comments on the merge request are locked to members only. |
......@@ -17792,6 +17816,21 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="mergerequestauthorworkspacesids"></a>`ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
 
### `MergeRequestDiffLlmSummary`
A diff summary generated by AI.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestdiffllmsummarycontent"></a>`content` | [`String!`](#string) | Content of the diff summary. |
| <a id="mergerequestdiffllmsummarycreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the diff summary was created. |
| <a id="mergerequestdiffllmsummarymergerequestdiffid"></a>`mergeRequestDiffId` | [`ID!`](#id) | ID of the Merge Request diff associated with the diff summary. |
| <a id="mergerequestdiffllmsummaryprovider"></a>`provider` | [`String!`](#string) | AI provider that generated the summary. |
| <a id="mergerequestdiffllmsummaryupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the diff summary was updated. |
| <a id="mergerequestdiffllmsummaryuser"></a>`user` | [`UserCore`](#usercore) | User associated with the diff summary. |
### `MergeRequestDiffRegistry`
 
Represents the Geo sync and verification state of a Merge Request diff.
......@@ -44,6 +44,11 @@ module MergeRequestType
' Returns `null` if `suggested_reviewers` feature flag is disabled.' \
' This flag is disabled by default and only available on GitLab.com' \
' because the feature is experimental and is subject to change without notice.'
field :diff_llm_summaries, ::Types::MergeRequests::DiffLlmSummaryType.connection_type,
null: true,
alpha: { milestone: '16.1' },
description: 'Diff summaries generated by AI'
end
def merge_trains_count
......
# frozen_string_literal: true
module Types
module MergeRequests
class DiffLlmSummaryType < ::Types::BaseObject
graphql_name 'MergeRequestDiffLlmSummary'
description 'A diff summary generated by AI.'
authorize :read_merge_request
field :merge_request_diff_id,
GraphQL::Types::ID,
null: false,
description: 'ID of the Merge Request diff associated with the diff summary.'
field :user, Types::UserType,
null: true,
description: 'User associated with the diff summary.'
field :provider, GraphQL::Types::String,
null: false,
description: 'AI provider that generated the summary.'
field :content, GraphQL::Types::String,
null: false,
description: 'Content of the diff summary.'
field :created_at, Types::TimeType,
null: false,
description: 'Timestamp of when the diff summary was created.'
field :updated_at, Types::TimeType,
null: false,
description: 'Timestamp of when the diff summary was updated.'
end
end
end
......@@ -394,6 +394,13 @@ def rebase_commit_is_different?(newrev)
rebase_commit_sha != newrev
end
def diff_llm_summaries
::MergeRequest::DiffLlmSummary
.includes(:user, merge_request_diff: [:merge_request])
.where(merge_request_diff_id: merge_request_diffs.recent)
.order(created_at: :desc)
end
private
def has_approved_license_check?
......
# frozen_string_literal: true
# rubocop:disable Style/ClassAndModuleChildren
class MergeRequest::DiffLlmSummaryPolicy < BasePolicy
delegate { @subject.merge_request_diff.project }
end
# rubocop:enable Style/ClassAndModuleChildren
......@@ -2,11 +2,12 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['MergeRequest'] do
RSpec.describe GitlabSchema.types['MergeRequest'], feature_category: :code_review_workflow do
it { expect(described_class).to have_graphql_fields(:approvals_required, :merge_trains_count, :approval_state).at_least }
it { expect(described_class).to have_graphql_field(:approved, complexity: 2, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:approvals_left, complexity: 2, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:has_security_reports, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:security_reports_up_to_date_on_target_branch, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:suggested_reviewers) }
it { expect(described_class).to have_graphql_field(:diff_llm_summaries) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['MergeRequestDiffLlmSummary'], feature_category: :code_review_workflow do
let(:fields) do
%i[
user
merge_request_diff_id
provider
content
created_at
updated_at
]
end
it { expect(described_class).to have_graphql_fields(fields) }
it { expect(described_class).to require_graphql_authorizations(:read_merge_request) }
end
......@@ -1529,4 +1529,19 @@ def create_mr(metrics_data = {})
end
end
end
describe '#diff_llm_summaries' do
let(:merge_request) { create(:merge_request) }
let!(:mr_diff_1) { create(:merge_request_diff, merge_request: merge_request) }
let!(:mr_diff_2) { create(:merge_request_diff, merge_request: merge_request) }
let!(:mr_diff_3) { create(:merge_request_diff, merge_request: merge_request) }
let!(:other_mr_diff) { create(:merge_request_diff) }
let!(:mr_diff_summary_1) { create(:merge_request_diff_llm_summary, merge_request_diff: mr_diff_1) }
let!(:mr_diff_summary_3) { create(:merge_request_diff_llm_summary, merge_request_diff: mr_diff_3) }
let!(:other_mr_diff_summary) { create(:merge_request_diff_llm_summary, merge_request_diff: other_mr_diff) }
it 'returns recent MergeRequest::DiffLlmSummary records associated to merge request diffs' do
expect(merge_request.diff_llm_summaries).to eq([mr_diff_summary_3, mr_diff_summary_1])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MergeRequest::DiffLlmSummaryPolicy, feature_category: :code_review_workflow do
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let_it_be(:mr_diff) { create(:merge_request_diff, merge_request: merge_request) }
let_it_be(:mr_diff_summary) { create(:merge_request_diff_llm_summary, merge_request_diff: mr_diff) }
subject(:policy) { described_class.new(user, mr_diff_summary) }
context 'when user is not permitted to read merge request' do
it { is_expected.to be_disallowed(:read_merge_request) }
end
context 'when user is permitted to read merge request' do
before do
project.add_developer(user)
end
it { is_expected.to be_allowed(:read_merge_request) }
end
end
......@@ -87,4 +87,46 @@
include_examples 'feature unavailable'
end
end
describe 'diffLlmSummaries' do
let(:mr_fields) { "diffLlmSummaries { nodes { mergeRequestDiffId content } }" }
context 'when there are MergeRequest::DiffLlmSummary records associated to MR' do
let!(:mr_diff_1) { create(:merge_request_diff, merge_request: merge_request) }
let!(:mr_diff_2) { create(:merge_request_diff, merge_request: merge_request) }
let!(:mr_diff_summary_1) { create(:merge_request_diff_llm_summary, merge_request_diff: mr_diff_1) }
let!(:mr_diff_summary_2) { create(:merge_request_diff_llm_summary, merge_request_diff: mr_diff_2) }
it 'returns the diff summaries' do
post_graphql(query, current_user: current_user)
expect(merge_request_graphql_data).to eq({
'diffLlmSummaries' => {
'nodes' => [
{
'mergeRequestDiffId' => mr_diff_2.id.to_s,
'content' => mr_diff_summary_2.content
},
{
'mergeRequestDiffId' => mr_diff_1.id.to_s,
'content' => mr_diff_summary_1.content
}
]
}
})
end
end
context 'when there are no MergeRequest::DiffLlmSummary records associated to MR' do
it 'returns empty nodes' do
post_graphql(query, current_user: current_user)
expect(merge_request_graphql_data).to eq({
'diffLlmSummaries' => {
'nodes' => []
}
})
end
end
end
end
......@@ -5,6 +5,6 @@
association :user, factory: :user
association :merge_request_diff, factory: :merge_request_diff
provider { 0 }
content { 'test' }
content { FFaker::Lorem.sentence }
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment