Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
97ae4a0d
Commit
97ae4a0d
authored
Oct 22, 2021
by
Daniel Tian
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add vulnerability list clone and move vulnerability counts component
parent
a2d053e6
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1100 additions
and
3 deletions
+1100
-3
ee/app/assets/javascripts/security_dashboard/components/group/vulnerability_report_development.vue
...ard/components/group/vulnerability_report_development.vue
+1
-1
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_counts.vue
...ents/shared/vulnerability_report/vulnerability_counts.vue
+0
-0
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue
...onents/shared/vulnerability_report/vulnerability_list.vue
+451
-0
ee/spec/frontend/security_dashboard/components/group/vulnerability_report_development_spec.js
...components/group/vulnerability_report_development_spec.js
+1
-1
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_counts_spec.js
.../shared/vulnerability_report/vulnerability_counts_spec.js
+1
-1
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js
...ts/shared/vulnerability_report/vulnerability_list_spec.js
+646
-0
No files found.
ee/app/assets/javascripts/security_dashboard/components/group/vulnerability_report_development.vue
View file @
97ae4a0d
...
...
@@ -2,7 +2,7 @@
import
countsQuery
from
'
ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql
'
;
import
createFlash
from
'
~/flash
'
;
import
{
s__
}
from
'
~/locale
'
;
import
VulnerabilityCounts
from
'
../shared/vulnerability_counts.vue
'
;
import
VulnerabilityCounts
from
'
../shared/vulnerability_
report/vulnerability_
counts.vue
'
;
export
default
{
components
:
{
VulnerabilityCounts
},
...
...
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_counts.vue
→
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_
report/vulnerability_
counts.vue
View file @
97ae4a0d
File moved
ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue
0 → 100644
View file @
97ae4a0d
<
script
>
import
{
GlFormCheckbox
,
GlLink
,
GlSprintf
,
GlTruncate
,
GlSkeletonLoading
,
GlTooltipDirective
,
GlTable
,
}
from
'
@gitlab/ui
'
;
import
DashboardHasNoVulnerabilities
from
'
ee/security_dashboard/components/shared/empty_states/dashboard_has_no_vulnerabilities.vue
'
;
import
FiltersProducedNoResults
from
'
ee/security_dashboard/components/shared/empty_states/filters_produced_no_results.vue
'
;
import
{
VULNERABILITIES_PER_PAGE
,
DASHBOARD_TYPES
}
from
'
ee/security_dashboard/store/constants
'
;
import
SeverityBadge
from
'
ee/vue_shared/security_reports/components/severity_badge.vue
'
;
import
convertReportType
from
'
ee/vue_shared/security_reports/store/utils/convert_report_type
'
;
import
getPrimaryIdentifier
from
'
ee/vue_shared/security_reports/store/utils/get_primary_identifier
'
;
import
FalsePositiveBadge
from
'
ee/vulnerabilities/components/false_positive_badge.vue
'
;
import
RemediatedBadge
from
'
ee/vulnerabilities/components/remediated_badge.vue
'
;
import
{
VULNERABILITY_STATES
}
from
'
ee/vulnerabilities/constants
'
;
import
{
formatDate
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
convertToSnakeCase
}
from
'
~/lib/utils/text_utility
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
AutoFixHelpText
from
'
../auto_fix_help_text.vue
'
;
import
IssuesBadge
from
'
../issues_badge.vue
'
;
import
SelectionSummary
from
'
../selection_summary.vue
'
;
import
VulnerabilityCommentIcon
from
'
../vulnerability_comment_icon.vue
'
;
export
default
{
components
:
{
GlFormCheckbox
,
GlLink
,
GlSkeletonLoading
,
GlSprintf
,
GlTable
,
GlTruncate
,
IssuesBadge
,
AutoFixHelpText
,
RemediatedBadge
,
FalsePositiveBadge
,
SelectionSummary
,
SeverityBadge
,
VulnerabilityCommentIcon
,
FiltersProducedNoResults
,
DashboardHasNoVulnerabilities
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
inject
:
{
hasVulnerabilities
:
{
default
:
false
,
},
hasJiraVulnerabilitiesIntegrationEnabled
:
{
default
:
false
,
},
canAdminVulnerability
:
{
default
:
false
,
},
dashboardType
:
{},
},
props
:
{
filters
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
vulnerabilities
:
{
type
:
Array
,
required
:
true
,
},
isLoading
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
shouldShowProjectNamespace
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
selectedVulnerabilities
:
{},
sortBy
:
'
severity
'
,
sortDesc
:
true
,
};
},
computed
:
{
isSortable
()
{
return
Boolean
(
this
.
$listeners
[
'
sort-changed
'
]);
},
isPipelineDashboard
()
{
return
this
.
dashboardType
===
DASHBOARD_TYPES
.
PIPELINE
;
},
hasAnyScannersOtherThanGitLab
()
{
return
this
.
vulnerabilities
.
some
(
(
v
)
=>
v
.
scanner
?.
vendor
!==
'
GitLab
'
&&
v
.
scanner
?.
vendor
!==
''
,
);
},
hasSelectedAllVulnerabilities
()
{
if
(
!
this
.
vulnerabilities
.
length
)
{
return
false
;
}
return
this
.
numOfSelectedVulnerabilities
===
this
.
vulnerabilities
.
length
;
},
numOfSelectedVulnerabilities
()
{
return
Object
.
keys
(
this
.
selectedVulnerabilities
).
length
;
},
shouldShowSelectionSummary
()
{
return
this
.
canAdminVulnerability
&&
this
.
numOfSelectedVulnerabilities
>
0
;
},
theadClass
()
{
return
this
.
shouldShowSelectionSummary
?
'
below-selection-summary
'
:
''
;
},
fields
()
{
const
baseFields
=
[
{
key
:
'
checkbox
'
,
class
:
'
checkbox
'
,
skip
:
!
this
.
canAdminVulnerability
,
},
{
key
:
'
detected
'
,
label
:
s__
(
'
Vulnerability|Detected
'
),
class
:
'
detected
'
,
sortable
:
this
.
isSortable
,
skip
:
this
.
isPipelineDashboard
,
},
{
key
:
'
state
'
,
label
:
s__
(
'
Vulnerability|Status
'
),
class
:
'
status
'
,
sortable
:
this
.
isSortable
,
},
{
key
:
'
severity
'
,
label
:
s__
(
'
Vulnerability|Severity
'
),
class
:
'
severity
'
,
sortable
:
this
.
isSortable
,
},
{
key
:
'
title
'
,
label
:
__
(
'
Description
'
),
class
:
'
description gl-word-break-all
'
,
sortable
:
this
.
isSortable
,
},
{
key
:
'
identifier
'
,
label
:
s__
(
'
Vulnerability|Identifier
'
),
class
:
'
identifier gl-word-break-all
'
,
},
{
key
:
'
reportType
'
,
label
:
s__
(
'
Reports|Tool
'
),
class
:
'
scanner
'
,
sortable
:
this
.
isSortable
,
},
{
key
:
'
activity
'
,
label
:
s__
(
'
Vulnerability|Activity
'
),
thClass
:
'
gl-text-right
'
,
class
:
'
activity
'
,
skip
:
this
.
isPipelineDashboard
,
},
].
filter
((
f
)
=>
!
f
.
skip
);
// Apply gl-bg-white! to every header.
baseFields
.
forEach
((
field
)
=>
{
field
.
thClass
=
[
field
.
thClass
,
'
gl-bg-white!
'
];
// eslint-disable-line no-param-reassign
});
return
baseFields
;
},
},
watch
:
{
filters
()
{
this
.
selectedVulnerabilities
=
{};
},
vulnerabilities
()
{
const
ids
=
new
Set
(
this
.
vulnerabilities
.
map
((
v
)
=>
v
.
id
));
Object
.
keys
(
this
.
selectedVulnerabilities
).
forEach
((
vulnerabilityId
)
=>
{
if
(
!
ids
.
has
(
vulnerabilityId
))
{
this
.
$delete
(
this
.
selectedVulnerabilities
,
vulnerabilityId
);
}
});
},
},
methods
:
{
createLocationString
(
location
)
{
const
{
image
,
file
,
startLine
,
path
}
=
location
;
if
(
image
)
{
return
image
;
}
if
(
file
&&
startLine
)
{
return
`
${
file
}
:
${
startLine
}
`
;
}
if
(
path
)
{
return
path
;
}
return
file
;
},
deselectVulnerability
(
vulnerabilityId
)
{
this
.
$delete
(
this
.
selectedVulnerabilities
,
vulnerabilityId
);
},
deselectAllVulnerabilities
()
{
this
.
selectedVulnerabilities
=
{};
},
extraIdentifierCount
(
identifiers
)
{
return
identifiers
?.
length
-
1
;
},
fileUrl
(
vulnerability
)
{
const
{
startLine
:
start
,
endLine
:
end
,
blobPath
}
=
vulnerability
.
location
;
const
lineNumber
=
end
>
start
?
`
${
start
}
-
${
end
}
`
:
start
;
if
(
!
blobPath
)
{
return
''
;
}
return
`
${
blobPath
}${
lineNumber
?
`#L
${
lineNumber
}
`
:
''
}
`
;
},
primaryIdentifier
(
identifiers
)
{
return
getPrimaryIdentifier
(
identifiers
,
'
externalType
'
);
},
isSelected
(
vulnerability
=
{})
{
return
Boolean
(
this
.
selectedVulnerabilities
[
vulnerability
.
id
]);
},
selectAllVulnerabilities
()
{
this
.
selectedVulnerabilities
=
this
.
vulnerabilities
.
reduce
((
acc
,
curr
)
=>
{
acc
[
curr
.
id
]
=
curr
;
return
acc
;
},
{});
},
shouldShowExtraIdentifierCount
(
identifiers
)
{
return
identifiers
?.
length
>
1
;
},
shouldShowVulnerabilityPath
(
item
)
{
return
Boolean
(
item
.
location
.
image
||
item
.
location
.
file
||
item
.
location
.
path
);
},
toggleAllVulnerabilities
()
{
if
(
this
.
hasSelectedAllVulnerabilities
)
{
this
.
deselectAllVulnerabilities
();
}
else
{
this
.
selectAllVulnerabilities
();
}
},
toggleVulnerability
(
vulnerability
)
{
if
(
this
.
selectedVulnerabilities
[
vulnerability
.
id
])
{
this
.
$delete
(
this
.
selectedVulnerabilities
,
`
${
vulnerability
.
id
}
`
);
}
else
{
this
.
$set
(
this
.
selectedVulnerabilities
,
`
${
vulnerability
.
id
}
`
,
vulnerability
);
}
},
gitlabIssues
(
item
)
{
return
item
.
issueLinks
?.
nodes
||
[];
},
externalIssues
(
item
)
{
return
item
.
externalIssueLinks
?.
nodes
||
[];
},
jiraIssues
(
item
)
{
return
this
.
externalIssues
(
item
).
filter
(({
issue
})
=>
issue
?.
externalTracker
===
'
jira
'
);
},
badgeIssues
(
item
)
{
return
this
.
hasJiraVulnerabilitiesIntegrationEnabled
?
this
.
jiraIssues
(
item
)
:
this
.
gitlabIssues
(
item
);
},
formatDate
(
item
)
{
return
formatDate
(
item
.
detectedAt
,
'
yyyy-mm-dd
'
);
},
formatDateTooltip
(
item
)
{
return
formatDate
(
item
.
detectedAt
);
},
hasComments
(
item
)
{
return
item
.
userNotesCount
>
0
;
},
useConvertReportType
(
reportType
)
{
return
convertReportType
(
reportType
);
},
handleSortChange
(
args
)
{
if
(
args
.
sortBy
)
{
this
.
$emit
(
'
sort-changed
'
,
{
...
args
,
sortBy
:
convertToSnakeCase
(
args
.
sortBy
)
});
}
},
getVulnerabilityState
(
state
=
''
)
{
const
stateName
=
state
.
toLowerCase
();
// Use the raw state name if we don't have a localization for it.
return
VULNERABILITY_STATES
[
stateName
]
||
stateName
;
},
},
VULNERABILITIES_PER_PAGE
,
};
</
script
>
<
template
>
<div
class=
"vulnerability-list"
>
<selection-summary
:selected-vulnerabilities=
"Object.values(selectedVulnerabilities)"
:visible=
"shouldShowSelectionSummary"
@
cancel-selection=
"deselectAllVulnerabilities"
@
vulnerability-updated=
"deselectVulnerability"
/>
<gl-table
v-if=
"filters"
:busy=
"isLoading"
:fields=
"fields"
:items=
"vulnerabilities"
:thead-class=
"theadClass"
:sort-desc=
"sortDesc"
:sort-by=
"sortBy"
sort-icon-left
no-local-sorting
stacked=
"sm"
class=
"vulnerability-list"
show-empty
responsive
hover
primary-key=
"id"
:tbody-tr-class=
"
{ 'gl-cursor-pointer': vulnerabilities.length }"
@sort-changed="handleSortChange"
@row-clicked="toggleVulnerability"
>
<template
#head
(
checkbox
)
>
<gl-form-checkbox
class=
"gl-m-0"
data-testid=
"vulnerability-checkbox-all"
:checked=
"hasSelectedAllVulnerabilities"
@
change=
"toggleAllVulnerabilities"
/>
</
template
>
<
template
#cell(checkbox)=
"{ item }"
>
<gl-form-checkbox
class=
"gl-display-inline-block! gl-m-0 gl-pointer-events-none"
data-testid=
"vulnerability-checkbox"
:checked=
"isSelected(item)"
@
change=
"toggleVulnerability(item)"
/>
</
template
>
<
template
#cell(detected)=
"{ item }"
>
<time
v-gl-tooltip
:data-testid=
"`detected-$
{item.id}`" :title="formatDateTooltip(item)">
{{
formatDate
(
item
)
}}
</time>
</
template
>
<
template
#cell(state)=
"{ item }"
>
<span
class=
"text-capitalize js-status"
>
{{
getVulnerabilityState
(
item
.
state
)
}}
</span>
</
template
>
<
template
#cell(severity)=
"{ item }"
>
<severity-badge
class=
"js-severity"
:severity=
"item.severity"
/>
</
template
>
<
template
#cell(title)=
"{ item }"
>
<div
class=
"gl-display-flex gl-flex-direction-column flex-sm-row gl-align-items-end align-items-sm-center"
:data-testid=
"`title-$
{item.id}`"
>
<gl-link
class=
"gl-text-body vulnerability-title js-description"
:href=
"item.vulnerabilityPath"
:data-qa-vulnerability-description=
"item.title || item.name"
data-qa-selector=
"vulnerability"
@
click=
"$emit('vulnerability-clicked', item)"
>
{{
item
.
title
||
item
.
name
}}
</gl-link>
<vulnerability-comment-icon
v-if=
"hasComments(item)"
:vulnerability=
"item"
/>
</div>
<div
v-if=
"item.location"
:data-testid=
"`location-$
{item.id}`"
class="gl-text-color-secondary gl-font-sm"
>
<div
v-if=
"shouldShowProjectNamespace"
>
{{
item
.
project
.
nameWithNamespace
}}
</div>
<div
v-if=
"shouldShowVulnerabilityPath(item)"
>
<gl-link
v-if=
"item.location.blobPath"
:href=
"fileUrl(item)"
>
<gl-truncate
:text=
"createLocationString(item.location)"
position=
"middle"
/>
</gl-link>
<gl-truncate
v-else
:text=
"createLocationString(item.location)"
position=
"middle"
/>
</div>
</div>
</
template
>
<
template
#cell(identifier)=
"{ item }"
>
<div
data-testid=
"vulnerability-identifier"
>
{{
primaryIdentifier
(
item
.
identifiers
)
}}
</div>
<div
v-if=
"shouldShowExtraIdentifierCount(item.identifiers)"
data-testid=
"vulnerability-more-identifiers"
class=
"gl-text-gray-300"
>
<gl-sprintf
:message=
"__('+ %
{count} more')">
<template
#count
>
{{
extraIdentifierCount
(
item
.
identifiers
)
}}
</
template
>
</gl-sprintf>
</div>
</template>
<
template
#cell(reportType)=
"{ item }"
>
<div
data-testid=
"vulnerability-report-type"
class=
"text-capitalize"
>
{{
useConvertReportType
(
item
.
reportType
)
}}
</div>
<div
v-if=
"hasAnyScannersOtherThanGitLab && item.scanner"
data-testid=
"vulnerability-vendor"
class=
"gl-text-gray-300"
>
{{
item
.
scanner
.
vendor
}}
</div>
</
template
>
<
template
#cell(activity)=
"{ item }"
>
<div
class=
"gl-display-flex gl-justify-content-end"
>
<auto-fix-help-text
v-if=
"item.mergeRequest"
:merge-request=
"item.mergeRequest"
/>
<issues-badge
v-if=
"badgeIssues(item).length > 0"
:issues=
"badgeIssues(item)"
:is-jira=
"hasJiraVulnerabilitiesIntegrationEnabled"
/>
<false-positive-badge
v-if=
"item.falsePositive"
class=
"gl-ml-3"
/>
<remediated-badge
v-if=
"item.resolvedOnDefaultBranch"
class=
"gl-ml-3"
/>
</div>
</
template
>
<
template
#table-busy
>
<gl-skeleton-loading
v-for=
"n in $options.VULNERABILITIES_PER_PAGE"
:key=
"n"
class=
"gl-m-3 js-skeleton-loader"
:lines=
"2"
/>
</
template
>
<
template
#empty
>
<filters-produced-no-results
v-if=
"hasVulnerabilities && !isLoading"
/>
<dashboard-has-no-vulnerabilities
v-else-if=
"!isLoading"
/>
</
template
>
</gl-table>
</div>
</template>
ee/spec/frontend/security_dashboard/components/group/vulnerability_report_development_spec.js
View file @
97ae4a0d
...
...
@@ -2,7 +2,7 @@ import { createLocalVue } from '@vue/test-utils';
import
VueApollo
from
'
vue-apollo
'
;
import
{
nextTick
}
from
'
vue
'
;
import
VulnerabilityReportDevelopment
from
'
ee/security_dashboard/components/group/vulnerability_report_development.vue
'
;
import
VulnerabilityCounts
from
'
ee/security_dashboard/components/shared/vulnerability_counts.vue
'
;
import
VulnerabilityCounts
from
'
ee/security_dashboard/components/shared/vulnerability_
report/vulnerability_
counts.vue
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
countsQuery
from
'
ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
...
...
ee/spec/frontend/security_dashboard/components/shared/vulnerability_counts_spec.js
→
ee/spec/frontend/security_dashboard/components/shared/vulnerability_
report/vulnerability_
counts_spec.js
View file @
97ae4a0d
import
{
GlCard
,
GlSkeletonLoading
}
from
'
@gitlab/ui
'
;
import
VulnerabilityCounts
from
'
ee/security_dashboard/components/shared/vulnerability_counts.vue
'
;
import
VulnerabilityCounts
from
'
ee/security_dashboard/components/shared/vulnerability_
report/vulnerability_
counts.vue
'
;
import
{
mountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
CRITICAL
,
...
...
ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js
0 → 100644
View file @
97ae4a0d
import
{
GlDeprecatedSkeletonLoading
as
GlSkeletonLoading
,
GlTable
,
GlTruncate
}
from
'
@gitlab/ui
'
;
import
{
capitalize
}
from
'
lodash
'
;
import
DashboardHasNoVulnerabilities
from
'
ee/security_dashboard/components/shared/empty_states/dashboard_has_no_vulnerabilities.vue
'
;
import
FiltersProducedNoResults
from
'
ee/security_dashboard/components/shared/empty_states/filters_produced_no_results.vue
'
;
import
IssuesBadge
from
'
ee/security_dashboard/components/shared/issues_badge.vue
'
;
import
SelectionSummary
from
'
ee/security_dashboard/components/shared/selection_summary.vue
'
;
import
VulnerabilityCommentIcon
from
'
ee/security_dashboard/components/shared/vulnerability_comment_icon.vue
'
;
import
VulnerabilityList
from
'
ee/security_dashboard/components/shared/vulnerability_list.vue
'
;
import
{
DASHBOARD_TYPES
}
from
'
ee/security_dashboard/store/constants
'
;
import
FalsePositiveBadge
from
'
ee/vulnerabilities/components/false_positive_badge.vue
'
;
import
RemediatedBadge
from
'
ee/vulnerabilities/components/remediated_badge.vue
'
;
import
{
trimText
}
from
'
helpers/text_helper
'
;
import
{
mountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
generateVulnerabilities
,
vulnerabilities
}
from
'
../../mock_data
'
;
describe
(
'
Vulnerability list component
'
,
()
=>
{
let
wrapper
;
const
createWrapper
=
({
props
=
{},
listeners
,
provide
=
{},
stubs
}
=
{})
=>
{
return
mountExtended
(
VulnerabilityList
,
{
propsData
:
{
vulnerabilities
:
[],
...
props
,
},
stubs
:
{
GlPopover
:
true
,
...
stubs
,
},
listeners
,
provide
:
()
=>
({
dashboardType
:
DASHBOARD_TYPES
.
PROJECT
,
noVulnerabilitiesSvgPath
:
'
#
'
,
dashboardDocumentation
:
'
#
'
,
emptyStateSvgPath
:
'
#
'
,
notEnabledScannersHelpPath
:
'
#
'
,
noPipelineRunScannersHelpPath
:
'
#
'
,
hasVulnerabilities
:
true
,
hasJiraVulnerabilitiesIntegrationEnabled
:
false
,
canAdminVulnerability
:
true
,
...
provide
,
}),
});
};
const
locationText
=
({
file
,
startLine
})
=>
`
${
file
}
:
${
startLine
}
`
;
const
findTable
=
()
=>
wrapper
.
findComponent
(
GlTable
);
const
findSortableColumn
=
()
=>
wrapper
.
find
(
'
[aria-sort="descending"]
'
);
const
findCell
=
(
label
)
=>
wrapper
.
find
(
`.js-
${
label
}
`
);
const
findRows
=
()
=>
wrapper
.
findAll
(
'
tbody tr
'
);
const
findRow
=
(
index
=
0
)
=>
findRows
().
at
(
index
);
const
findColumn
=
(
className
)
=>
wrapper
.
find
(
`[role="columnheader"].
${
className
}
`
);
const
findRowById
=
(
id
)
=>
wrapper
.
find
(
`tbody tr[data-pk="
${
id
}
"`
);
const
findAutoFixBulbInRow
=
(
row
)
=>
row
.
find
(
'
[data-testid="vulnerability-solutions-bulb"]
'
);
const
findIssuesBadge
=
(
index
=
0
)
=>
wrapper
.
findAllComponents
(
IssuesBadge
).
at
(
index
);
const
findRemediatedBadge
=
()
=>
wrapper
.
findComponent
(
RemediatedBadge
);
const
findSelectionSummary
=
()
=>
wrapper
.
findComponent
(
SelectionSummary
);
const
findRowVulnerabilityCommentIcon
=
(
row
)
=>
findRow
(
row
).
findComponent
(
VulnerabilityCommentIcon
);
const
findDataCell
=
(
label
)
=>
wrapper
.
findByTestId
(
label
);
const
findDataCells
=
(
label
)
=>
wrapper
.
findAll
(
`[data-testid="
${
label
}
"]`
);
const
findLocationCell
=
(
id
)
=>
wrapper
.
findByTestId
(
`location-
${
id
}
`
);
const
findTitleCell
=
(
id
)
=>
wrapper
.
findByTestId
(
`title-
${
id
}
`
);
const
findLocationTextWrapper
=
(
cell
)
=>
cell
.
find
(
GlTruncate
);
const
findFiltersProducedNoResults
=
()
=>
wrapper
.
findComponent
(
FiltersProducedNoResults
);
const
findDashboardHasNoVulnerabilities
=
()
=>
wrapper
.
findComponent
(
DashboardHasNoVulnerabilities
);
const
findVendorNames
=
()
=>
wrapper
.
findByTestId
(
'
vulnerability-vendor
'
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
with vulnerabilities
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
}
});
});
it
(
'
should render a list of vulnerabilities
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.js-status
'
)).
toHaveLength
(
newVulnerabilities
.
length
);
});
it
(
'
should correctly render the status
'
,
()
=>
{
const
cell
=
findCell
(
'
status
'
);
expect
(
cell
.
text
()).
toBe
(
capitalize
(
newVulnerabilities
[
0
].
state
));
});
it
(
'
should correctly render the severity
'
,
()
=>
{
const
cell
=
findCell
(
'
severity
'
);
expect
(
cell
.
text
().
toLowerCase
()).
toBe
(
newVulnerabilities
[
0
].
severity
);
});
it
(
'
should correctly render the description
'
,
()
=>
{
const
cell
=
findCell
(
'
description
'
);
expect
(
cell
.
text
()).
toBe
(
newVulnerabilities
[
0
].
title
);
});
it
(
'
should display the remediated badge
'
,
()
=>
{
expect
(
findRemediatedBadge
().
exists
()).
toBe
(
true
);
});
it
(
'
should display autoFixIcon for first Item
'
,
()
=>
{
expect
(
findAutoFixBulbInRow
(
findRow
(
0
)).
exists
()).
toBe
(
true
);
});
it
(
'
should not display autoFixIcon for second Item
'
,
()
=>
{
expect
(
findAutoFixBulbInRow
(
findRow
(
1
)).
exists
()).
toBe
(
false
);
});
it
(
'
should correctly render the identifier cell
'
,
()
=>
{
const
identifiers
=
findDataCells
(
'
vulnerability-identifier
'
);
const
extraIdentifierCounts
=
findDataCells
(
'
vulnerability-more-identifiers
'
);
const
firstIdentifiers
=
newVulnerabilities
[
0
].
identifiers
;
expect
(
identifiers
.
at
(
0
).
text
()).
toBe
(
firstIdentifiers
[
0
].
name
);
expect
(
trimText
(
extraIdentifierCounts
.
at
(
0
).
text
())).
toContain
(
`
${
firstIdentifiers
.
length
-
1
}
more`
,
);
expect
(
identifiers
.
at
(
1
).
text
()).
toBe
(
newVulnerabilities
[
1
].
identifiers
[
0
].
name
);
expect
(
extraIdentifierCounts
).
toHaveLength
(
1
);
});
it
(
'
should correctly render the report type cell
'
,
()
=>
{
const
cells
=
findDataCells
(
'
vulnerability-report-type
'
);
expect
(
cells
.
at
(
0
).
text
()).
toBe
(
'
SAST
'
);
expect
(
cells
.
at
(
1
).
text
()).
toBe
(
'
Dependency Scanning
'
);
expect
(
cells
.
at
(
2
).
text
()).
toBe
(
'
Custom scanner without translation
'
);
expect
(
cells
.
at
(
3
).
text
()).
toBe
(
''
);
});
it
(
'
should correctly render the vulnerability vendor if the vulnerability vendor does exist
'
,
()
=>
{
const
cells
=
findDataCells
(
'
vulnerability-vendor
'
);
expect
(
cells
.
at
(
0
).
text
()).
toBe
(
'
GitLab
'
);
});
it
(
'
should correctly render an empty string if the vulnerability vendor does not exist
'
,
()
=>
{
const
cells
=
findDataCells
(
'
vulnerability-vendor
'
);
expect
(
cells
.
at
(
3
).
text
()).
toBe
(
''
);
});
it
(
'
should not show the selection summary if no vulnerabilities are selected
'
,
()
=>
{
expect
(
findSelectionSummary
().
props
(
'
visible
'
)).
toBe
(
false
);
});
it
(
'
should show the selection summary when a checkbox is selected
'
,
async
()
=>
{
findDataCell
(
'
vulnerability-checkbox
'
).
setChecked
(
true
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
findSelectionSummary
().
props
(
'
visible
'
)).
toBe
(
true
);
});
it
(
'
should sync selected vulnerabilities when the vulnerability list is updated
'
,
async
()
=>
{
findDataCell
(
'
vulnerability-checkbox
'
).
setChecked
(
true
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
findSelectionSummary
().
props
(
'
selectedVulnerabilities
'
)).
toHaveLength
(
1
);
wrapper
.
setProps
({
vulnerabilities
:
[]
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
findSelectionSummary
().
props
(
'
visible
'
)).
toBe
(
false
);
});
it
(
'
should uncheck a selected vulnerability after the vulnerability is updated
'
,
async
()
=>
{
const
checkbox
=
()
=>
findDataCell
(
'
vulnerability-checkbox
'
);
checkbox
().
setChecked
(
true
);
expect
(
checkbox
().
element
.
checked
).
toBe
(
true
);
await
wrapper
.
vm
.
$nextTick
();
findSelectionSummary
().
vm
.
$emit
(
'
vulnerability-updated
'
,
newVulnerabilities
[
0
].
id
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
checkbox
().
element
.
checked
).
toBe
(
false
);
});
describe
.
each
([
true
,
false
])(
'
issues badge when "hasJiraVulnerabilitiesIntegrationEnabled" is set to "%s"
'
,
(
hasJiraVulnerabilitiesIntegrationEnabled
)
=>
{
beforeEach
(()
=>
{
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
generateVulnerabilities
()
},
provide
:
{
hasJiraVulnerabilitiesIntegrationEnabled
},
});
});
it
(
'
should display the issues badge for the first item
'
,
()
=>
{
expect
(
findIssuesBadge
(
0
).
exists
()).
toBe
(
true
);
});
it
(
'
should not display the issues badge for the second item
'
,
()
=>
{
expect
(()
=>
findIssuesBadge
(
1
)).
toThrow
();
});
it
(
'
should render the badge as Jira issues
'
,
()
=>
{
expect
(
findIssuesBadge
(
0
).
props
(
'
isJira
'
)).
toBe
(
hasJiraVulnerabilitiesIntegrationEnabled
);
});
},
);
});
describe
(
'
when user has no permission to admin vulnerabilities
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
},
provide
:
{
canAdminVulnerability
:
false
,
},
});
});
it
(
'
should not show the checkboxes
'
,
()
=>
{
expect
(
findDataCell
(
'
vulnerability-checkbox-all
'
).
exists
()).
toBe
(
false
);
expect
(
findDataCell
(
'
vulnerability-checkbox
'
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
when displayed on instance or group level dashboard
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
,
shouldShowProjectNamespace
:
true
},
});
});
it
(
'
should display the vulnerability locations for images
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
0
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
location
.
image
,
position
:
'
middle
'
,
});
});
it
(
'
should display the vulnerability locations for code
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
1
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
locationText
(
location
),
position
:
'
middle
'
,
});
});
it
(
'
should display the vulnerability locations for code with no line data
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
2
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
location
.
file
,
position
:
'
middle
'
,
});
});
it
(
'
should not display the vulnerability locations for vulnerabilities without a location
'
,
()
=>
{
const
{
id
,
project
}
=
newVulnerabilities
[
4
];
const
cellText
=
findLocationCell
(
id
).
text
();
expect
(
cellText
).
toEqual
(
project
.
nameWithNamespace
);
expect
(
cellText
).
not
.
toContain
(
'
:
'
);
});
it
(
'
should display the vulnerability locations for path
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
5
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
location
.
path
,
position
:
'
middle
'
,
});
});
});
describe
(
'
when displayed on a project level dashboard
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
,
shouldShowIdentifier
:
true
,
shouldShowReportType
:
true
,
},
});
});
it
(
'
should not display the vulnerability group/project locations for images
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
0
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
not
.
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
location
.
image
,
position
:
'
middle
'
,
});
});
it
(
'
should display the detected time
'
,
()
=>
{
const
{
id
}
=
newVulnerabilities
[
1
];
const
cell
=
findDataCell
(
`detected-
${
id
}
`
);
expect
(
cell
.
text
()).
toEqual
(
`2020-07-22`
);
expect
(
cell
.
attributes
(
'
title
'
)).
toEqual
(
'
Jul 22, 2020 7:31pm UTC
'
);
});
it
(
'
should display the vulnerability locations for code
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
1
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
not
.
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
locationText
(
location
),
position
:
'
middle
'
,
});
});
it
(
'
should make the file path linkable
'
,
()
=>
{
const
{
id
,
location
}
=
newVulnerabilities
[
1
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
find
(
'
a
'
).
attributes
(
'
href
'
)).
toBe
(
`
${
location
.
blobPath
}
#L
${
location
.
startLine
}
`
);
});
it
(
'
should not make the file path linkable if blobPath is missing
'
,
()
=>
{
const
{
id
}
=
newVulnerabilities
[
0
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
find
(
'
a
'
).
exists
()).
toBe
(
false
);
});
it
(
'
should not display the vulnerability group/project locations for code with no line data
'
,
()
=>
{
const
{
id
,
project
,
location
}
=
newVulnerabilities
[
2
];
const
cell
=
findLocationCell
(
id
);
expect
(
cell
.
text
()).
not
.
toContain
(
project
.
nameWithNamespace
);
expect
(
findLocationTextWrapper
(
cell
).
props
()).
toEqual
({
text
:
location
.
file
,
position
:
'
middle
'
,
});
});
});
describe
(
'
when has an issue associated
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
[
0
].
issueLinks
=
{
nodes
:
[
{
issue
:
{
title
:
'
my-title
'
,
iid
:
114
,
state
:
'
opened
'
,
webUrl
:
'
http://localhost/issues/~/114
'
,
},
},
],
};
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
}
});
});
it
(
'
should emit "vulnerability-clicked" with the vulnerability as a payload when a vulnerability-link is clicked
'
,
async
()
=>
{
const
clickedEventName
=
'
vulnerability-clicked
'
;
const
vulnerability
=
newVulnerabilities
[
1
];
const
link
=
findTitleCell
(
vulnerability
.
id
).
find
(
'
a
'
);
expect
(
wrapper
.
emitted
(
clickedEventName
)).
toBe
(
undefined
);
await
link
.
trigger
(
'
click
'
);
const
emittedEvents
=
wrapper
.
emitted
(
clickedEventName
);
expect
(
emittedEvents
).
toHaveLength
(
1
);
expect
(
emittedEvents
[
0
][
0
]).
toBe
(
vulnerability
);
});
});
describe
(
'
when has comments
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
[
0
].
userNotesCount
=
1
;
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
}
});
});
it
(
'
should render the comments badge on the first vulnerability
'
,
()
=>
{
expect
(
findRowVulnerabilityCommentIcon
(
0
).
exists
()).
toBe
(
true
);
});
it
(
'
should not render the comments badge on the second vulnerability
'
,
()
=>
{
expect
(
findRowVulnerabilityCommentIcon
(
1
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
when GitLab is the only scanner in the reports
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
=
newVulnerabilities
.
map
((
v
)
=>
({
...
v
,
scanner
:
{
vendor
:
'
GitLab
'
},
}));
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
,
shouldShowReportType
:
true
,
},
});
});
it
(
'
should not render the vendor name
'
,
()
=>
{
expect
(
findVendorNames
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when vendor name is not provided in the reports
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
=
newVulnerabilities
.
map
((
v
)
=>
({
...
v
,
scanner
:
{
vendor
:
''
}
}));
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
,
shouldShowReportType
:
true
,
},
});
});
it
(
'
should not render the vendor name
'
,
()
=>
{
expect
(
findVendorNames
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when there are other scanners in the report
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
[
0
].
scanner
=
{
vendor
:
'
GitLab
'
};
newVulnerabilities
[
1
].
scanner
=
{
vendor
:
'
Third Party Scanner
'
};
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
,
shouldShowReportType
:
true
,
},
});
});
it
(
'
should not render the vendor name
'
,
()
=>
{
expect
(
findVendorNames
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when a vulnerability has a false positive
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
[
0
].
falsePositive
=
true
;
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
},
provide
:
{
falsePositiveDocUrl
:
'
/docs
'
,
canViewFalsePositive
:
true
,
},
});
});
it
(
'
should render the false positive info badge on the first vulnerability
'
,
()
=>
{
const
row
=
findRow
(
0
);
const
badge
=
row
.
findComponent
(
FalsePositiveBadge
);
expect
(
badge
.
exists
()).
toEqual
(
true
);
});
it
(
'
should not render the false positive info badge on the second vulnerability
'
,
()
=>
{
const
row
=
findRow
(
1
);
const
badge
=
row
.
findComponent
(
FalsePositiveBadge
);
expect
(
badge
.
exists
()).
toEqual
(
false
);
});
});
describe
(
'
when a vulnerability is resolved on the default branch
'
,
()
=>
{
let
newVulnerabilities
;
beforeEach
(()
=>
{
newVulnerabilities
=
generateVulnerabilities
();
newVulnerabilities
[
0
].
resolvedOnDefaultBranch
=
true
;
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
:
newVulnerabilities
}
});
});
it
(
'
should render the remediated info badge on the first vulnerability
'
,
()
=>
{
const
row
=
findRow
(
0
);
const
badge
=
row
.
find
(
RemediatedBadge
);
expect
(
badge
.
exists
()).
toEqual
(
true
);
});
it
(
'
should not render the remediated info badge on the second vulnerability
'
,
()
=>
{
const
row
=
findRow
(
1
);
const
badge
=
row
.
find
(
RemediatedBadge
);
expect
(
badge
.
exists
()).
toEqual
(
false
);
});
});
describe
(
'
when loading
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createWrapper
({
props
:
{
isLoading
:
true
}
});
});
it
(
'
should show the loading state
'
,
()
=>
{
expect
(
findCell
(
'
status
'
).
exists
()).
toEqual
(
false
);
expect
(
wrapper
.
find
(
GlSkeletonLoading
).
exists
()).
toEqual
(
true
);
});
});
describe
(
'
with no vulnerabilities
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createWrapper
({
props
:
{
filters
:
{
someFilter
:
'
true
'
}
}
});
});
it
(
'
should show the empty state
'
,
()
=>
{
expect
(
findCell
(
'
status
'
).
exists
()).
toEqual
(
false
);
expect
(
findFiltersProducedNoResults
().
exists
()).
toEqual
(
true
);
expect
(
findDashboardHasNoVulnerabilities
().
exists
()).
toEqual
(
false
);
});
});
describe
(
'
with vulnerabilities when there are filters
'
,
()
=>
{
it
.
each
`
state
${[
'
DETECTED
'
]}
${[
'
DISMISSED
'
]}
${[]}
${[
'
DETECTED
'
,
'
DISMISSED
'
]}
`
(
'
should only show vulnerabilities that match filter $state
'
,
(
state
)
=>
{
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
,
filters
:
{
state
}
}
});
const
filteredVulnerabilities
=
vulnerabilities
.
filter
((
x
)
=>
state
.
length
?
state
.
includes
(
x
.
state
)
:
true
,
);
expect
(
findRows
().
length
).
toBe
(
filteredVulnerabilities
.
length
);
filteredVulnerabilities
.
forEach
((
vulnerability
)
=>
{
expect
(
findRowById
(
vulnerability
.
id
).
exists
()).
toBe
(
true
);
});
});
});
describe
(
'
when has a sort-changed listener defined
'
,
()
=>
{
let
spy
;
beforeEach
(()
=>
{
spy
=
jest
.
fn
();
wrapper
=
createWrapper
({
listeners
:
{
'
sort-changed
'
:
spy
},
});
});
it
(
'
is sortable
'
,
()
=>
{
expect
(
findSortableColumn
().
attributes
(
'
class
'
)).
toContain
(
'
severity
'
);
});
it
(
'
triggers the listener when sortBy is not an empty value
'
,
()
=>
{
const
args
=
{
sortBy
:
'
severity
'
,
sortDesc
:
false
};
findTable
().
vm
.
$emit
(
'
sort-changed
'
,
args
);
expect
(
spy
).
toHaveBeenCalledWith
(
args
);
});
it
(
'
triggers the listener when sortBy is camelCased and transforms it to snake_case
'
,
()
=>
{
const
args
=
{
sortBy
:
'
reportType
'
,
sortDesc
:
false
};
findTable
().
vm
.
$emit
(
'
sort-changed
'
,
args
);
expect
(
spy
).
toHaveBeenCalledWith
({
...
args
,
sortBy
:
'
report_type
'
});
});
it
(
'
does not trigger the listener when sortBy is an empty value
'
,
()
=>
{
findTable
().
vm
.
$emit
(
'
sort-changed
'
,
{});
expect
(
spy
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
when does not have a sort-changed listener defined
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createWrapper
();
});
it
(
'
is not sortable
'
,
()
=>
{
expect
(
findSortableColumn
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
row click
'
,
()
=>
{
const
findRowCheckbox
=
(
index
)
=>
findRow
(
index
).
find
(
'
[data-testid="vulnerability-checkbox"]
'
);
beforeEach
(()
=>
{
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
}
});
});
it
(
'
will select and deselect vulnerabilities
'
,
async
()
=>
{
const
rowCount
=
vulnerabilities
.
length
;
const
rowsToClick
=
[
0
,
1
,
2
];
const
clickRows
=
()
=>
rowsToClick
.
forEach
((
row
)
=>
findRow
(
row
).
trigger
(
'
click
'
));
const
expectRowCheckboxesToBe
=
(
condition
)
=>
{
for
(
let
i
=
0
;
i
<
rowCount
;
i
+=
1
)
expect
(
findRowCheckbox
(
i
).
element
.
checked
).
toBe
(
condition
(
i
));
};
clickRows
();
await
wrapper
.
vm
.
$nextTick
();
expectRowCheckboxesToBe
((
i
)
=>
rowsToClick
.
includes
(
i
));
clickRows
();
await
wrapper
.
vm
.
$nextTick
();
expectRowCheckboxesToBe
(()
=>
false
);
});
});
describe
(
'
when it is the pipeline dashboard
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createWrapper
({
props
:
{
vulnerabilities
},
provide
:
{
dashboardType
:
DASHBOARD_TYPES
.
PIPELINE
},
stubs
:
{
GlTable
,
},
});
});
it
.
each
([[
'
detected
'
],
[
'
activity
'
]])(
'
does not render %s column
'
,
(
className
)
=>
{
expect
(
findColumn
(
className
).
exists
()).
toBe
(
false
);
});
it
.
each
([[
'
status
'
],
[
'
severity
'
],
[
'
description
'
],
[
'
identifier
'
],
[
'
scanner
'
]])(
'
renders %s column
'
,
(
className
)
=>
{
expect
(
findColumn
(
className
).
exists
()).
toBe
(
true
);
},
);
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment