From a4ab6ed4bbf148426dcb48669498feb64c371ff0 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Tue, 23 Sep 2025 11:50:42 -0700 Subject: [PATCH 1/6] Agentic VR badges As part of the initiative to add an agentic flow for resolving SAST vulnerabilities, we are adding new badges and states to the vulnerability report. - AI fix in progress badge - AI fixed badge This change is behind a new feature flag `agentic_sast_vr_ui`. The AI fix in progress badge expects a yet to be implemented graphql field `aiFixInProgress` which will be based on related workflows. This now uses a client-side resolver to simulate some of the vulnerabilities having a fix in progress. --- .../shared/ai_fix_in_progress_badge.vue | 39 +++++ .../components/shared/ai_fixed_badge.vue | 24 +++ .../vulnerability_list.vue | 23 ++- .../fragments/vulnerability.fragment.graphql | 4 + .../security_dashboard/graphql/provider.js | 3 +- .../security_dashboard/graphql/resolvers.js | 25 +++ .../vulnerability_report_controller.rb | 1 + .../feature_flags/wip/agentic_sast_vr_ui.yml | 10 ++ .../components/mock_data.js | 16 ++ .../shared/ai_fix_in_progress_badge_spec.js | 49 ++++++ .../components/shared/ai_fixed_badge_spec.js | 41 +++++ .../vulnerability_list_spec.js | 143 +++++++++++++++++- locale/gitlab.pot | 9 ++ 13 files changed, 380 insertions(+), 7 deletions(-) create mode 100644 ee/app/assets/javascripts/security_dashboard/components/shared/ai_fix_in_progress_badge.vue create mode 100644 ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue create mode 100644 ee/app/assets/javascripts/security_dashboard/graphql/resolvers.js create mode 100644 ee/config/feature_flags/wip/agentic_sast_vr_ui.yml create mode 100644 ee/spec/frontend/security_dashboard/components/shared/ai_fix_in_progress_badge_spec.js create mode 100644 ee/spec/frontend/security_dashboard/components/shared/ai_fixed_badge_spec.js diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fix_in_progress_badge.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fix_in_progress_badge.vue new file mode 100644 index 00000000000000..541f997ed4ceb7 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fix_in_progress_badge.vue @@ -0,0 +1,39 @@ + + + diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue new file mode 100644 index 00000000000000..b51da54c99c96f --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue @@ -0,0 +1,24 @@ + + + diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue index c82bf5a6cb890d..fb40e92d9e8fb1 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue @@ -28,8 +28,10 @@ import { formatDate } from '~/lib/utils/datetime_utility'; import MergeRequestBadge from '../merge_request_badge.vue'; import IssuesBadge from '../issues_badge.vue'; import SolutionBadge from '../solution_badge.vue'; +import AiFixedBadge from '../ai_fixed_badge.vue'; import AiResolutionBadge from '../ai_resolution_badge.vue'; import VulnerabilityCommentIcon from '../vulnerability_comment_icon.vue'; +import AiFixInProgressBadge from '../ai_fix_in_progress_badge.vue'; import VulnerabilityPath from './vulnerability_path.vue'; import SelectionSummary from './selection_summary.vue'; import VulnerabilityListStatus from './vulnerability_list_status.vue'; @@ -61,7 +63,9 @@ export default { DashboardHasNoVulnerabilities, Portal, SolutionBadge, + AiFixedBadge, AiResolutionBadge, + AiFixInProgressBadge, }, directives: { GlTooltip: GlTooltipDirective, @@ -262,6 +266,20 @@ export default { hasComments(item) { return item.userNotesCount > 0; }, + hasAiMergeRequest(item) { + return ( + this.glFeatures.agenticSastVrUi && + item.mergeRequest?.author && + !item.mergeRequest?.author?.human + ); + }, + showAiResolutionBadgeAgentic(item) { + if (!this.glFeatures.agenticSastVrUi) { + return true; + } + + return !item.mergeRequest && !item.aiFixInProgress; + }, useConvertReportType(reportType) { return convertReportType(reportType); }, @@ -513,6 +531,7 @@ export default { data-testid="vulnerability-issue-created-badge-content" :data-qa-badge-description="item.title || item.name" /> + + diff --git a/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql b/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql index eead82686eb510..b34f9b1a8b9423 100644 --- a/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql +++ b/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql @@ -38,6 +38,9 @@ fragment VulnerabilityFragment on Vulnerability { webUrl state iid + author { + human + } } identifiers { externalType @@ -73,4 +76,5 @@ fragment VulnerabilityFragment on Vulnerability { expectedToBeArchivedOn } reachability + aiFixInProgress @client } diff --git a/ee/app/assets/javascripts/security_dashboard/graphql/provider.js b/ee/app/assets/javascripts/security_dashboard/graphql/provider.js index e80539164dd5bd..cb4da4d5c0ebf5 100644 --- a/ee/app/assets/javascripts/security_dashboard/graphql/provider.js +++ b/ee/app/assets/javascripts/security_dashboard/graphql/provider.js @@ -6,6 +6,7 @@ import createDefaultClient from '~/lib/graphql'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_FORBIDDEN } from '~/lib/utils/http_status'; import * as Sentry from '~/sentry/sentry_browser_wrapper'; +import resolvers from './resolvers'; Vue.use(VueApollo); @@ -72,6 +73,6 @@ export const cacheConfig = { }; // Create Apollo client with cache config and security dashboard local resolvers -export const defaultClient = createDefaultClient({}, { cacheConfig }); +export const defaultClient = createDefaultClient(resolvers, { cacheConfig }); export default new VueApollo({ defaultClient }); diff --git a/ee/app/assets/javascripts/security_dashboard/graphql/resolvers.js b/ee/app/assets/javascripts/security_dashboard/graphql/resolvers.js new file mode 100644 index 00000000000000..452b1cee575884 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/graphql/resolvers.js @@ -0,0 +1,25 @@ +const aiFixInProgress = (vulnerability) => { + if (!gon.features?.agenticSastVrUi) { + return false; + } + + // Mock logic: vulnerabilities with IDs ending in certain digits have workflows in progress + const vulnerabilityId = vulnerability.id; + if (!vulnerabilityId) return false; + + const numericId = vulnerabilityId.split('/').pop(); + if (!numericId) return false; + + const lastDigit = parseInt(numericId.slice(-1), 10); + return [1, 3, 7, 9].includes(lastDigit); +}; + +export const vulnerabilityResolvers = { + Vulnerability: { + aiFixInProgress, + }, +}; + +export default { + ...vulnerabilityResolvers, +}; diff --git a/ee/app/controllers/projects/security/vulnerability_report_controller.rb b/ee/app/controllers/projects/security/vulnerability_report_controller.rb index 059f42a9af0840..5bb8e6b9fc2bcd 100644 --- a/ee/app/controllers/projects/security/vulnerability_report_controller.rb +++ b/ee/app/controllers/projects/security/vulnerability_report_controller.rb @@ -12,6 +12,7 @@ class VulnerabilityReportController < Projects::ApplicationController push_frontend_feature_flag(:existing_jira_issue_attachment_from_vulnerability_bulk_action, @project, type: :wip) push_frontend_feature_flag(:validity_checks, @project, type: :wip) push_frontend_feature_flag(:vulnerability_report_type_scanner_filter, @project, type: :beta) + push_frontend_feature_flag(:agentic_sast_vr_ui, @project, type: :wip) push_frontend_ability(ability: :resolve_vulnerability_with_ai, resource: @project, user: current_user) push_frontend_ability(ability: :access_advanced_vulnerability_management, resource: @project, user: current_user) diff --git a/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml b/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml new file mode 100644 index 00000000000000..22ba2264d04e00 --- /dev/null +++ b/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml @@ -0,0 +1,10 @@ +--- +name: agentic_sast_vr_ui +description: UI development related to agentic SAST vulnerability resolution +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/556999 +introduced_by_url: +rollout_issue_url: +milestone: '18.4' +group: group::compliance +type: wip +default_enabled: false diff --git a/ee/spec/frontend/security_dashboard/components/mock_data.js b/ee/spec/frontend/security_dashboard/components/mock_data.js index 6883a350f64710..a6eea5bf2a8696 100644 --- a/ee/spec/frontend/security_dashboard/components/mock_data.js +++ b/ee/spec/frontend/security_dashboard/components/mock_data.js @@ -75,6 +75,7 @@ export const projectClusters = { export const clusterImageScanningVulnerability = { mergeRequest: null, + aiFixInProgress: false, __typename: 'Vulnerability', id: 'gid://gitlab/Vulnerability/22087293', title: 'CVE-2021-29921', @@ -121,7 +122,11 @@ export const containerScanningForRegistryVulnerability = { webUrl: 'www.testmr.com/1', state: 'status_warning', iid: 1, + author: { + human: true, + }, }, + aiFixInProgress: false, identifiers: [ { externalType: 'cve', @@ -193,7 +198,11 @@ export const generateVulnerabilities = () => [ webUrl: 'www.testmr.com/1', state: 'status_warning', iid: 1, + author: { + human: true, + }, }, + aiFixInProgress: false, identifiers: [ { externalType: 'cve', @@ -268,6 +277,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: false, issueLinks: [], mergeRequest: null, + aiFixInProgress: true, identifiers: [ { externalType: 'gemnasium', @@ -311,6 +321,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: false, issueLinks: [], mergeRequest: null, + aiFixInProgress: false, identifiers: [], dismissalReason: null, cvss: [], @@ -351,6 +362,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: true, issueLinks: [], mergeRequest: null, + aiFixInProgress: false, identifiers: [], dismissalReason: null, cvss: [], @@ -388,6 +400,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: true, issueLinks: [], mergeRequest: null, + aiFixInProgress: false, identifiers: [], dismissalReason: null, cvss: [], @@ -421,6 +434,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: false, issueLinks: [], mergeRequest: null, + aiFixInProgress: false, identifiers: [], dismissalReason: null, cvss: [], @@ -456,6 +470,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: false, issueLinks: [], mergeRequest: null, + aiFixInProgress: false, identifiers: [], dismissalReason: null, cvss: [], @@ -492,6 +507,7 @@ export const generateVulnerabilities = () => [ resolvedOnDefaultBranch: false, issueLinks: [], mergeRequest: null, + aiFixInProgress: false, identifiers: [ { externalType: 'secret_detection', diff --git a/ee/spec/frontend/security_dashboard/components/shared/ai_fix_in_progress_badge_spec.js b/ee/spec/frontend/security_dashboard/components/shared/ai_fix_in_progress_badge_spec.js new file mode 100644 index 00000000000000..95617de297c858 --- /dev/null +++ b/ee/spec/frontend/security_dashboard/components/shared/ai_fix_in_progress_badge_spec.js @@ -0,0 +1,49 @@ +import { GlBadge, GlIcon, GlAnimatedLoaderIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import AiFixInProgressBadge from 'ee/security_dashboard/components/shared/ai_fix_in_progress_badge.vue'; + +describe('AiFixInProgressBadge', () => { + let wrapper; + + const createWrapper = () => { + wrapper = shallowMountExtended(AiFixInProgressBadge); + }; + + beforeEach(() => { + createWrapper(); + }); + + const findBadge = () => wrapper.findComponent(GlBadge); + const tooltipMessage = 'AI is currently working to resolve this vulnerability'; + + it('renders a badge with the correct variant', () => { + expect(findBadge().props()).toMatchObject({ + variant: 'info', + }); + }); + + it('renders the duo icon', () => { + expect(wrapper.findComponent(GlIcon).props()).toMatchObject({ + name: 'tanuki-ai', + }); + }); + + it('renders the correct message', () => { + expect(wrapper.findByTestId('ai-fix-in-progress-badge-text').text()).toBe('AI Generating Fix'); + }); + + it('renders the tooltip message', () => { + expect(findBadge().attributes('title')).toBe(tooltipMessage); + }); + + it('renders the accessible tooltip text', () => { + const tooltip = wrapper.findByTestId('ai-fix-in-progress-badge-tooltip'); + + expect(tooltip.text()).toBe(tooltipMessage); + expect(tooltip.classes()).toContain('gl-sr-only'); + }); + + it('renders the animated loader icon', () => { + expect(wrapper.findComponent(GlAnimatedLoaderIcon).exists()).toBe(true); + }); +}); diff --git a/ee/spec/frontend/security_dashboard/components/shared/ai_fixed_badge_spec.js b/ee/spec/frontend/security_dashboard/components/shared/ai_fixed_badge_spec.js new file mode 100644 index 00000000000000..77f66023929465 --- /dev/null +++ b/ee/spec/frontend/security_dashboard/components/shared/ai_fixed_badge_spec.js @@ -0,0 +1,41 @@ +import { GlBadge, GlIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import AiFixedBadge from 'ee/security_dashboard/components/shared/ai_fixed_badge.vue'; + +describe('AiFixedBadge', () => { + let wrapper; + + const createWrapper = () => { + wrapper = shallowMountExtended(AiFixedBadge); + }; + + beforeEach(() => { + createWrapper(); + }); + + const findBadge = () => wrapper.findComponent(GlBadge); + const tooltipMessage = 'AI has created a merge request to resolve this vulnerability'; + + it('renders a badge with the correct variant', () => { + expect(findBadge().props()).toMatchObject({ + variant: 'info', + }); + }); + + it('renders the duo icon', () => { + expect(wrapper.findComponent(GlIcon).props()).toMatchObject({ + name: 'tanuki-ai', + }); + }); + + it('renders the tooltip message', () => { + expect(findBadge().attributes('title')).toBe(tooltipMessage); + }); + + it('renders the accessible tooltip text', () => { + const tooltip = wrapper.findByTestId('ai-fixed-badge-tooltip'); + + expect(tooltip.text()).toBe(tooltipMessage); + expect(tooltip.classes()).toContain('gl-sr-only'); + }); +}); diff --git a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js index 70371d8bc93d31..a40ba49ade2ad0 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js @@ -12,6 +12,8 @@ import VulnerabilityListStatus from 'ee/security_dashboard/components/shared/vul import MergeRequestBadge from 'ee/security_dashboard/components/shared/merge_request_badge.vue'; import SolutionBadge from 'ee/security_dashboard/components/shared/solution_badge.vue'; import AiResolutionBadge from 'ee/security_dashboard/components/shared/ai_resolution_badge.vue'; +import AiFixInProgressBadge from 'ee/security_dashboard/components/shared/ai_fix_in_progress_badge.vue'; +import AiFixedBadge from 'ee/security_dashboard/components/shared/ai_fixed_badge.vue'; import { DASHBOARD_TYPE_PROJECT } from 'ee/security_dashboard/constants'; import FalsePositiveBadge from 'ee/vulnerabilities/components/false_positive_badge.vue'; import ArchivalBadge from 'ee/vulnerabilities/components/archival_badge.vue'; @@ -897,11 +899,12 @@ describe('Vulnerability list component', () => { describe('AI vulnerability resolution badge', () => { const findAiResolutionBadgeInRow = findComponentBadgeInRow(AiResolutionBadge); - const createWrapperWithVulnerabilities = ( + const createWrapperWithVulnerabilities = ({ resolveVulnerabilityWithAi, aiResolutionAvailable, aiResolutionEnabled, - ) => { + agenticSastVrUi = false, + }) => { newVulnerabilities = generateVulnerabilities(); newVulnerabilities[0].aiResolutionAvailable = aiResolutionAvailable; @@ -910,6 +913,7 @@ describe('Vulnerability list component', () => { createWrapper({ props: { vulnerabilities: newVulnerabilities }, provide: { + glFeatures: { agenticSastVrUi }, glAbilities: { resolveVulnerabilityWithAi }, }, }); @@ -926,21 +930,150 @@ describe('Vulnerability list component', () => { ${false} | ${false} | ${true} | ${false} ${false} | ${false} | ${false} | ${false} `( - 'displays the badge "$shouldDisplayBadge" if resolveVulnerabilityWithAi=$resolveVulnerabilityWithAi, aiResolutionAvailable=$aiResolutionAvailable, aiResolutionEnabled=$aiResolutionEnabled', + 'displays the badge "$shouldDisplayBadge" if resolveVulnerabilityWithAi=$resolveVulnerabilityWithAi, aiResolutionAvailable=$aiResolutionAvailable, aiResolutionEnabled=$aiResolutionEnabled (works same when agentic_sast_vr_ui feature flag is disabled)', ({ resolveVulnerabilityWithAi, aiResolutionAvailable, aiResolutionEnabled, shouldDisplayBadge, }) => { - createWrapperWithVulnerabilities( + createWrapperWithVulnerabilities({ resolveVulnerabilityWithAi, aiResolutionAvailable, aiResolutionEnabled, - ); + }); expect(findAiResolutionBadgeInRow(0).exists()).toBe(shouldDisplayBadge); }, ); + + describe('when agentic_sast_vr_ui feature flag is enabled', () => { + it.each` + mergeRequest | aiFixInProgress | shouldDisplayBadge | description + ${null} | ${false} | ${true} | ${'no merge request and no AI fix in progress'} + ${null} | ${true} | ${false} | ${'no merge request but AI fix is in progress'} + ${{ id: 'mr-1', webUrl: 'test.com' }} | ${false} | ${false} | ${'merge request exists and no AI fix in progress'} + ${{ id: 'mr-1', webUrl: 'test.com' }} | ${true} | ${false} | ${'merge request exists and AI fix is in progress'} + `( + 'displays AI resolution badge "$shouldDisplayBadge" when vulnerability has $description', + ({ mergeRequest, aiFixInProgress, shouldDisplayBadge }) => { + newVulnerabilities = generateVulnerabilities(); + newVulnerabilities[0].aiResolutionAvailable = true; + newVulnerabilities[0].aiResolutionEnabled = true; + newVulnerabilities[0].mergeRequest = mergeRequest; + newVulnerabilities[0].aiFixInProgress = aiFixInProgress; + + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: true }, + glAbilities: { resolveVulnerabilityWithAi: true }, + }, + }); + + expect(findAiResolutionBadgeInRow(0).exists()).toBe(shouldDisplayBadge); + }, + ); + }); + }); + + describe('AI fix in progress badge', () => { + const findAiFixInProgressBadgeInRow = findComponentBadgeInRow(AiFixInProgressBadge); + + describe('when agentic_sast_vr_ui feature flag is enabled', () => { + it('displays AI fix in progress badge when AI fix is in progress', () => { + newVulnerabilities = generateVulnerabilities(); + newVulnerabilities[0].aiFixInProgress = true; + + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: true }, + }, + }); + + expect(findAiFixInProgressBadgeInRow(0).exists()).toBe(true); + }); + + it('does not display AI fix in progress badge when AI fix is not in progress', () => { + newVulnerabilities = generateVulnerabilities(); + newVulnerabilities[0].aiFixInProgress = false; + + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: true }, + }, + }); + + expect(findAiFixInProgressBadgeInRow(0).exists()).toBe(false); + }); + }); + + describe('when agentic_sast_vr_ui feature flag is disabled', () => { + it('does not display AI fix in progress badge even when AI fix is in progress', () => { + newVulnerabilities = generateVulnerabilities(); + newVulnerabilities[0].aiFixInProgress = true; + + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: false }, + }, + }); + + expect(findAiFixInProgressBadgeInRow(0).exists()).toBe(false); + }); + }); + }); + + describe('AI fixed badge', () => { + const findAiFixedBadgeInRow = findComponentBadgeInRow(AiFixedBadge); + + describe('when agentic_sast_vr_ui feature flag is enabled', () => { + it.each` + mergeRequest | shouldDisplayBadge | description + ${null} | ${false} | ${'no merge request'} + ${{ id: 'mr-1', webUrl: 'test.com', author: { human: true } }} | ${false} | ${'merge request with human author'} + ${{ id: 'mr-1', webUrl: 'test.com', author: { human: false } }} | ${true} | ${'merge request with AI author'} + ${{ id: 'mr-1', webUrl: 'test.com', author: null }} | ${false} | ${'merge request with no author'} + ${{ id: 'mr-1', webUrl: 'test.com' }} | ${false} | ${'merge request with undefined author'} + `( + 'displays AI fixed badge "$shouldDisplayBadge" when vulnerability has $description', + ({ mergeRequest, shouldDisplayBadge }) => { + newVulnerabilities = generateVulnerabilities(); + newVulnerabilities[0].mergeRequest = mergeRequest; + + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: true }, + }, + }); + + expect(findAiFixedBadgeInRow(0).exists()).toBe(shouldDisplayBadge); + }, + ); + }); + + describe('when agentic_sast_vr_ui feature flag is disabled', () => { + it('does not display AI fixed badge even when merge request has AI author', () => { + newVulnerabilities = generateVulnerabilities(); + newVulnerabilities[0].mergeRequest = { + id: 'mr-1', + webUrl: 'test.com', + author: { human: false }, + }; + + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: false }, + }, + }); + + expect(findAiFixedBadgeInRow(0).exists()).toBe(false); + }); + }); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6a301fe62049d8..9521c79e9bc3ca 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -71999,6 +71999,15 @@ msgstr "" msgid "Vulnerability|A solution is available for this vulnerability" msgstr "" +msgid "Vulnerability|AI Generating Fix" +msgstr "" + +msgid "Vulnerability|AI has created a merge request to resolve this vulnerability" +msgstr "" + +msgid "Vulnerability|AI is currently working to resolve this vulnerability" +msgstr "" + msgid "Vulnerability|Active secret" msgstr "" -- GitLab From b9e5f46bb3bc540ae168d49c6792fbcf32a6d032 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Tue, 23 Sep 2025 12:00:38 -0700 Subject: [PATCH 2/6] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: GitLab Duo --- .../security_dashboard/components/shared/ai_fixed_badge.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue index b51da54c99c96f..fc86ce359222d8 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/ai_fixed_badge.vue @@ -3,6 +3,7 @@ import { GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { s__ } from '~/locale'; export default { + name: 'AiFixedBadge', components: { GlIcon, GlBadge, -- GitLab From 32d124dd611dda6761256a6ad32520617a4c9c6c Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Tue, 23 Sep 2025 12:05:29 -0700 Subject: [PATCH 3/6] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: DANGER_GITLAB_API_TOKEN --- ee/config/feature_flags/wip/agentic_sast_vr_ui.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml b/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml index 22ba2264d04e00..e6a0b1c93d4d68 100644 --- a/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml +++ b/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml @@ -2,7 +2,7 @@ name: agentic_sast_vr_ui description: UI development related to agentic SAST vulnerability resolution feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/556999 -introduced_by_url: +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/206089 rollout_issue_url: milestone: '18.4' group: group::compliance -- GitLab From cc16b20bcc709fd82fb75adc6f3afeb795122415 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Tue, 23 Sep 2025 12:46:17 -0700 Subject: [PATCH 4/6] Add ID to author graphql --- .../graphql/fragments/vulnerability.fragment.graphql | 1 + ee/spec/frontend/security_dashboard/components/mock_data.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql b/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql index b34f9b1a8b9423..2da03506a3d439 100644 --- a/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql +++ b/ee/app/assets/javascripts/security_dashboard/graphql/fragments/vulnerability.fragment.graphql @@ -39,6 +39,7 @@ fragment VulnerabilityFragment on Vulnerability { state iid author { + id human } } diff --git a/ee/spec/frontend/security_dashboard/components/mock_data.js b/ee/spec/frontend/security_dashboard/components/mock_data.js index a6eea5bf2a8696..ae36540f781554 100644 --- a/ee/spec/frontend/security_dashboard/components/mock_data.js +++ b/ee/spec/frontend/security_dashboard/components/mock_data.js @@ -123,6 +123,7 @@ export const containerScanningForRegistryVulnerability = { state: 'status_warning', iid: 1, author: { + id: 1, human: true, }, }, @@ -199,6 +200,7 @@ export const generateVulnerabilities = () => [ state: 'status_warning', iid: 1, author: { + id: 1, human: true, }, }, -- GitLab From 59f6bee4c27b97e993f41439c398ac8eec933047 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Wed, 24 Sep 2025 09:09:38 -0700 Subject: [PATCH 5/6] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: SAM FIGUEROA --- ee/config/feature_flags/wip/agentic_sast_vr_ui.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml b/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml index e6a0b1c93d4d68..99c341d972f716 100644 --- a/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml +++ b/ee/config/feature_flags/wip/agentic_sast_vr_ui.yml @@ -4,7 +4,7 @@ description: UI development related to agentic SAST vulnerability resolution feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/556999 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/206089 rollout_issue_url: -milestone: '18.4' +milestone: '18.5' group: group::compliance type: wip default_enabled: false -- GitLab From 1f28513b00c1b82d1b41ee802cf1fb0d6c6e83e5 Mon Sep 17 00:00:00 2001 From: Scott Hampton Date: Fri, 3 Oct 2025 14:19:23 -0700 Subject: [PATCH 6/6] Remove one shot VR badge We have decided to no longer show the one shot VR badge once the feature flag is enabled. --- .../vulnerability_list.vue | 9 +---- .../vulnerability_list_spec.js | 37 +++++++------------ 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue index fb40e92d9e8fb1..cf7e481a2a5192 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue @@ -273,13 +273,6 @@ export default { !item.mergeRequest?.author?.human ); }, - showAiResolutionBadgeAgentic(item) { - if (!this.glFeatures.agenticSastVrUi) { - return true; - } - - return !item.mergeRequest && !item.aiFixInProgress; - }, useConvertReportType(reportType) { return convertReportType(reportType); }, @@ -543,7 +536,7 @@ export default { item.aiResolutionAvailable && item.aiResolutionEnabled && glAbilities.resolveVulnerabilityWithAi && - showAiResolutionBadgeAgentic(item) + !glFeatures.agenticSastVrUi " /> diff --git a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js index a40ba49ade2ad0..1bbed3385c87a4 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js @@ -947,32 +947,21 @@ describe('Vulnerability list component', () => { ); describe('when agentic_sast_vr_ui feature flag is enabled', () => { - it.each` - mergeRequest | aiFixInProgress | shouldDisplayBadge | description - ${null} | ${false} | ${true} | ${'no merge request and no AI fix in progress'} - ${null} | ${true} | ${false} | ${'no merge request but AI fix is in progress'} - ${{ id: 'mr-1', webUrl: 'test.com' }} | ${false} | ${false} | ${'merge request exists and no AI fix in progress'} - ${{ id: 'mr-1', webUrl: 'test.com' }} | ${true} | ${false} | ${'merge request exists and AI fix is in progress'} - `( - 'displays AI resolution badge "$shouldDisplayBadge" when vulnerability has $description', - ({ mergeRequest, aiFixInProgress, shouldDisplayBadge }) => { - newVulnerabilities = generateVulnerabilities(); - newVulnerabilities[0].aiResolutionAvailable = true; - newVulnerabilities[0].aiResolutionEnabled = true; - newVulnerabilities[0].mergeRequest = mergeRequest; - newVulnerabilities[0].aiFixInProgress = aiFixInProgress; + it('should not display AI vulnerability resolution badge', () => { + newVulnerabilities = generateVulnerabilities(); - createWrapper({ - props: { vulnerabilities: newVulnerabilities }, - provide: { - glFeatures: { agenticSastVrUi: true }, - glAbilities: { resolveVulnerabilityWithAi: true }, - }, - }); + newVulnerabilities[0].aiResolutionAvailable = true; + newVulnerabilities[0].aiResolutionEnabled = true; - expect(findAiResolutionBadgeInRow(0).exists()).toBe(shouldDisplayBadge); - }, - ); + createWrapper({ + props: { vulnerabilities: newVulnerabilities }, + provide: { + glFeatures: { agenticSastVrUi: true }, + }, + }); + + expect(findAiResolutionBadgeInRow(0).exists()).toBe(false); + }); }); }); -- GitLab