Commit 7efa826c authored by Tetiana Chupryna's avatar Tetiana Chupryna Committed by Mark Florian

Add field hasSolutions for Vulnerability

parent 614e2c10
...@@ -24911,6 +24911,11 @@ type Vulnerability implements Noteable { ...@@ -24911,6 +24911,11 @@ type Vulnerability implements Noteable {
last: Int last: Int
): VulnerabilityExternalIssueLinkConnection! ): VulnerabilityExternalIssueLinkConnection!
"""
Indicates whether there is a solution available for this vulnerability.
"""
hasSolutions: Boolean
""" """
GraphQL ID of the vulnerability GraphQL ID of the vulnerability
""" """
......
...@@ -72489,6 +72489,20 @@ ...@@ -72489,6 +72489,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "hasSolutions",
"description": "Indicates whether there is a solution available for this vulnerability.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "id", "name": "id",
"description": "GraphQL ID of the vulnerability", "description": "GraphQL ID of the vulnerability",
...@@ -3748,6 +3748,7 @@ Represents a vulnerability. ...@@ -3748,6 +3748,7 @@ Represents a vulnerability.
| `discussions` | DiscussionConnection! | All discussions on this noteable | | `discussions` | DiscussionConnection! | All discussions on this noteable |
| `dismissedAt` | Time | Timestamp of when the vulnerability state was changed to dismissed | | `dismissedAt` | Time | Timestamp of when the vulnerability state was changed to dismissed |
| `externalIssueLinks` | VulnerabilityExternalIssueLinkConnection! | List of external issue links related to the vulnerability | | `externalIssueLinks` | VulnerabilityExternalIssueLinkConnection! | List of external issue links related to the vulnerability |
| `hasSolutions` | Boolean | Indicates whether there is a solution available for this vulnerability. |
| `id` | ID! | GraphQL ID of the vulnerability | | `id` | ID! | GraphQL ID of the vulnerability |
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. | | `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
| `issueLinks` | VulnerabilityIssueLinkConnection! | List of issue links related to the vulnerability | | `issueLinks` | VulnerabilityIssueLinkConnection! | List of issue links related to the vulnerability |
......
...@@ -436,7 +436,7 @@ export default { ...@@ -436,7 +436,7 @@ export default {
<template #cell(activity)="{ item }"> <template #cell(activity)="{ item }">
<div class="gl-display-flex gl-justify-content-end"> <div class="gl-display-flex gl-justify-content-end">
<gl-badge <gl-badge
v-if="item.solutions" v-if="item.hasSolutions"
v-gl-tooltip v-gl-tooltip
data-testid="vulnerability-solutions-bulb" data-testid="vulnerability-solutions-bulb"
variant="neutral" variant="neutral"
......
...@@ -23,7 +23,7 @@ query project( ...@@ -23,7 +23,7 @@ query project(
) { ) {
nodes { nodes {
...Vulnerability ...Vulnerability
solutions hasSolutions
} }
pageInfo { pageInfo {
...PageInfo ...PageInfo
......
...@@ -74,6 +74,10 @@ module Types ...@@ -74,6 +74,10 @@ module Types
field :dismissed_at, Types::TimeType, null: true, field :dismissed_at, Types::TimeType, null: true,
description: 'Timestamp of when the vulnerability state was changed to dismissed' description: 'Timestamp of when the vulnerability state was changed to dismissed'
field :has_solutions, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates whether there is a solution available for this vulnerability.',
resolver_method: :has_solutions?
def user_notes_count def user_notes_count
::Gitlab::Graphql::Aggregations::Vulnerabilities::LazyUserNotesCountAggregate.new(context, object) ::Gitlab::Graphql::Aggregations::Vulnerabilities::LazyUserNotesCountAggregate.new(context, object)
end end
...@@ -101,5 +105,9 @@ module Types ...@@ -101,5 +105,9 @@ module Types
def project def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end end
def has_solutions?
object.finding&.remediations&.any?
end
end end
end end
---
title: Add field hasSolutions for Vulnerability GraphQL type
merge_request: 48820
author:
type: added
...@@ -2,7 +2,7 @@ export const generateVulnerabilities = () => [ ...@@ -2,7 +2,7 @@ export const generateVulnerabilities = () => [
{ {
id: 'id_0', id: 'id_0',
detectedAt: '2020-07-29T15:36:54Z', detectedAt: '2020-07-29T15:36:54Z',
solutions: true, hasSolutions: true,
identifiers: [ identifiers: [
{ {
externalType: 'cve', externalType: 'cve',
...@@ -35,7 +35,7 @@ export const generateVulnerabilities = () => [ ...@@ -35,7 +35,7 @@ export const generateVulnerabilities = () => [
{ {
id: 'id_1', id: 'id_1',
detectedAt: '2020-07-22T19:31:24Z', detectedAt: '2020-07-22T19:31:24Z',
solutions: false, hasSolutions: false,
identifiers: [ identifiers: [
{ {
externalType: 'gemnasium', externalType: 'gemnasium',
......
...@@ -29,6 +29,7 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do ...@@ -29,6 +29,7 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do
dismissed_at dismissed_at
notes notes
external_issue_links external_issue_links
has_solutions
discussions] discussions]
end end
...@@ -67,4 +68,37 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do ...@@ -67,4 +68,37 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do
) )
end end
end end
describe 'has_solutions' do
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
name
vulnerabilities {
nodes {
hasSolutions
}
}
}
}
)
end
context 'N+1 queries' do
it 'avoids N+1 database queries' do
pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/292993')
control_count = ActiveRecord::QueryRecorder.new { GitlabSchema.execute(query, context: { current_user: user }) }.count
create(:vulnerability, :with_remediation, project: project)
create(:vulnerability, :with_remediation, project: project)
expect { GitlabSchema.execute(query, context: { current_user: user }) }.not_to exceed_query_limit(control_count)
result = GitlabSchema.execute(query, context: { current_user: user }).to_h
vulnerability = result.dig('data', 'project', 'vulnerabilities', 'nodes').first
expect(vulnerability['hasSolution']).to be_truthy
end
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