Commit 7f696037 authored by Stan Hu's avatar Stan Hu

Merge branch '213742_return_related_issue_information_for_vulnerabilities' into 'master'

Add issueLinks field to Vulnerability type

See merge request gitlab-org/gitlab!33173
parents 6fc0a489 64355323
...@@ -12613,6 +12613,36 @@ type Vulnerability { ...@@ -12613,6 +12613,36 @@ type Vulnerability {
""" """
id: ID! id: ID!
"""
List of issue links related to the vulnerability
"""
issueLinks(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
Filter issue links by link type
"""
linkType: [VulnerabilityIssueLinkType!]
): VulnerabilityIssueLinkConnection!
""" """
Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability
""" """
...@@ -12695,6 +12725,69 @@ type VulnerabilityEdge { ...@@ -12695,6 +12725,69 @@ type VulnerabilityEdge {
node: Vulnerability node: Vulnerability
} }
"""
Represents an issue link of a vulnerability.
"""
type VulnerabilityIssueLink {
"""
GraphQL ID of the vulnerability
"""
id: ID!
"""
The issue attached to issue link
"""
issue: Issue!
"""
Type of the issue link
"""
linkType: VulnerabilityIssueLinkType!
}
"""
The connection type for VulnerabilityIssueLink.
"""
type VulnerabilityIssueLinkConnection {
"""
A list of edges.
"""
edges: [VulnerabilityIssueLinkEdge]
"""
A list of nodes.
"""
nodes: [VulnerabilityIssueLink]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type VulnerabilityIssueLinkEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: VulnerabilityIssueLink
}
"""
The type of the issue link related to a vulnerability.
"""
enum VulnerabilityIssueLinkType {
CREATED
RELATED
}
""" """
Represents a vulnerability location. The fields with data will depend on the vulnerability report type Represents a vulnerability location. The fields with data will depend on the vulnerability report type
""" """
......
...@@ -37150,6 +37150,81 @@ ...@@ -37150,6 +37150,81 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "issueLinks",
"description": "List of issue links related to the vulnerability",
"args": [
{
"name": "linkType",
"description": "Filter issue links by link type",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "VulnerabilityIssueLinkType",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "VulnerabilityIssueLinkConnection",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "location", "name": "location",
"description": "Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability", "description": "Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability",
...@@ -37404,6 +37479,208 @@ ...@@ -37404,6 +37479,208 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "VulnerabilityIssueLink",
"description": "Represents an issue link of a vulnerability.",
"fields": [
{
"name": "id",
"description": "GraphQL ID of the vulnerability",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue attached to issue link",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "linkType",
"description": "Type of the issue link",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "VulnerabilityIssueLinkType",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityIssueLinkConnection",
"description": "The connection type for VulnerabilityIssueLink.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "VulnerabilityIssueLinkEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "VulnerabilityIssueLink",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityIssueLinkEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityIssueLink",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "VulnerabilityIssueLinkType",
"description": "The type of the issue link related to a vulnerability.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "RELATED",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CREATED",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{ {
"kind": "UNION", "kind": "UNION",
"name": "VulnerabilityLocation", "name": "VulnerabilityLocation",
...@@ -1871,6 +1871,16 @@ Represents a vulnerability. ...@@ -1871,6 +1871,16 @@ Represents a vulnerability.
| `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource | | `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource |
| `vulnerabilityPath` | String | URL to the vulnerability's details page | | `vulnerabilityPath` | String | URL to the vulnerability's details page |
## VulnerabilityIssueLink
Represents an issue link of a vulnerability.
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | GraphQL ID of the vulnerability |
| `issue` | Issue! | The issue attached to issue link |
| `linkType` | VulnerabilityIssueLinkType! | Type of the issue link |
## VulnerabilityLocationContainerScanning ## VulnerabilityLocationContainerScanning
Represents the location of a vulnerability found by a container security scan Represents the location of a vulnerability found by a container security scan
......
# frozen_string_literal: true
module Resolvers
module Vulnerabilities
class IssueLinksResolver < BaseResolver
type Types::Vulnerability::IssueLinkType, null: true
argument :link_type, [Types::Vulnerability::IssueLinkTypeEnum],
required: false,
description: 'Filter issue links by link type'
delegate :issue_links, to: :object, private: true
def resolve(link_type: nil, **)
issue_links.by_link_type(link_type)
end
end
end
end
# frozen_string_literal: true
module Types
module Vulnerability
# rubocop: disable Graphql/AuthorizeTypes
class IssueLinkType < BaseObject
graphql_name 'VulnerabilityIssueLink'
description 'Represents an issue link of a vulnerability.'
field :id, GraphQL::ID_TYPE, null: false,
description: 'GraphQL ID of the vulnerability'
field :link_type, ::Types::Vulnerability::IssueLinkTypeEnum, null: false,
description: "Type of the issue link"
field :issue, ::Types::IssueType, null: false,
description: 'The issue attached to issue link'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
# frozen_string_literal: true
module Types
module Vulnerability
class IssueLinkTypeEnum < BaseEnum
graphql_name 'VulnerabilityIssueLinkType'
description 'The type of the issue link related to a vulnerability.'
::Vulnerabilities::IssueLink.link_types.keys.each do |link_type|
value link_type.to_s.upcase, value: link_type.to_s
end
end
end
end
...@@ -34,6 +34,10 @@ module Types ...@@ -34,6 +34,10 @@ module Types
description: "URL to the vulnerability's details page", description: "URL to the vulnerability's details page",
resolve: -> (obj, _args, _ctx) { ::Gitlab::Routing.url_helpers.project_security_vulnerability_path(obj.project, obj) } resolve: -> (obj, _args, _ctx) { ::Gitlab::Routing.url_helpers.project_security_vulnerability_path(obj.project, obj) }
field :issue_links, ::Types::Vulnerability::IssueLinkType.connection_type, null: false,
description: "List of issue links related to the vulnerability",
resolver: Resolvers::Vulnerabilities::IssueLinksResolver
field :location, VulnerabilityLocationType, null: true, field :location, VulnerabilityLocationType, null: true,
description: 'Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability', description: 'Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability',
resolve: -> (obj, _args, _ctx) { obj.finding&.location&.merge(report_type: obj.report_type) } resolve: -> (obj, _args, _ctx) { obj.finding&.location&.merge(report_type: obj.report_type) }
......
...@@ -17,5 +17,7 @@ module Vulnerabilities ...@@ -17,5 +17,7 @@ module Vulnerabilities
message: N_('already has a "created" issue link') message: N_('already has a "created" issue link')
}, },
if: :created? if: :created?
scope :by_link_type, -> (link_type) { link_type ? where(link_type: link_type.downcase) : all }
end end
end end
---
title: Introduce `issueLinks` field for VulnerabilityType on GraphQL API
merge_request: 33173
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
describe Resolvers::Vulnerabilities::IssueLinksResolver do
include GraphqlHelpers
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:vulnerability) { create(:vulnerability) }
let_it_be(:related_issue) { create(:vulnerabilities_issue_link, :related, vulnerability: vulnerability) }
let_it_be(:created_issue) { create(:vulnerabilities_issue_link, :created, vulnerability: vulnerability) }
subject { resolve(described_class, obj: vulnerability, args: filters, ctx: { current_user: user }) }
context 'when there is no filter given' do
let(:filters) { {} }
it { is_expected.to match_array([related_issue, created_issue]) }
end
context 'when the link_type filter is given' do
context 'when the filter is `CREATED`' do
let(:filters) { { link_type: 'CREATED' } }
it { is_expected.to match_array([created_issue]) }
end
context 'when the filter is `RELATED`' do
let(:filters) { { link_type: 'RELATED' } }
it { is_expected.to match_array([related_issue]) }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['VulnerabilityIssueLinkType'] do
let(:expected_values) { %w[RELATED CREATED] }
subject { described_class.values.keys }
it { is_expected.to contain_exactly(*expected_values) }
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['VulnerabilityIssueLink'] do
let(:expected_fields) { %i[id link_type issue] }
subject { described_class }
it { is_expected.to have_graphql_fields(expected_fields) }
end
...@@ -8,7 +8,7 @@ describe GitlabSchema.types['Vulnerability'] do ...@@ -8,7 +8,7 @@ describe GitlabSchema.types['Vulnerability'] do
let_it_be(:vulnerability) { create(:vulnerability, project: project) } let_it_be(:vulnerability) { create(:vulnerability, project: project) }
let(:fields) do let(:fields) do
%i[userPermissions id title description user_notes_count state severity report_type vulnerability_path location project] %i[userPermissions id title description user_notes_count state severity report_type vulnerability_path location project issueLinks]
end end
before do before do
......
...@@ -78,4 +78,29 @@ describe Vulnerabilities::IssueLink do ...@@ -78,4 +78,29 @@ describe Vulnerabilities::IssueLink do
end end
end end
end end
describe '.by_link_type' do
let_it_be(:created_issue_link) { create(:vulnerabilities_issue_link, :created) }
let_it_be(:related_issue_link) { create(:vulnerabilities_issue_link, :related) }
subject { described_class.by_link_type(link_type).to_a }
context 'when the given argument is `nil`' do
let(:link_type) { nil }
it { is_expected.to match_array([created_issue_link, related_issue_link]) }
end
context 'when the given argument is an uppercase string enum value' do
let(:link_type) { 'CREATED' }
it { is_expected.to match_array([created_issue_link]) }
end
context 'when the given argument is an uppercase symbol enum value' do
let(:link_type) { :RELATED }
it { is_expected.to match_array([related_issue_link]) }
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment