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
ab76aa69
Commit
ab76aa69
authored
Jul 28, 2021
by
Daniel Tian
Committed by
Dmitriy Zaporozhets (DZ)
Jul 28, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add more robust vulnerability report project filter
parent
ec4bc97f
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
348 additions
and
27 deletions
+348
-27
config/feature_flags/development/vuln_report_new_project_filter.yml
...ture_flags/development/vuln_report_new_project_filter.yml
+8
-0
ee/app/assets/javascripts/security_dashboard/components/shared/filters/filters_layout.vue
...ty_dashboard/components/shared/filters/filters_layout.vue
+27
-5
ee/app/assets/javascripts/security_dashboard/components/shared/filters/project_filter.vue
...ty_dashboard/components/shared/filters/project_filter.vue
+246
-0
ee/app/assets/javascripts/security_dashboard/components/shared/filters/simple_filter.vue
...ity_dashboard/components/shared/filters/simple_filter.vue
+14
-8
ee/app/assets/javascripts/security_dashboard/graphql/queries/group_projects.query.graphql
...ty_dashboard/graphql/queries/group_projects.query.graphql
+2
-2
ee/app/assets/javascripts/security_dashboard/graphql/queries/instance_projects.query.graphql
...dashboard/graphql/queries/instance_projects.query.graphql
+2
-2
ee/app/assets/javascripts/security_dashboard/helpers.js
ee/app/assets/javascripts/security_dashboard/helpers.js
+3
-2
ee/app/controllers/groups/security/vulnerabilities_controller.rb
...controllers/groups/security/vulnerabilities_controller.rb
+1
-0
ee/app/controllers/security/vulnerabilities_controller.rb
ee/app/controllers/security/vulnerabilities_controller.rb
+1
-0
ee/spec/frontend/security_dashboard/components/shared/filters/filters_layout_spec.js
...ashboard/components/shared/filters/filters_layout_spec.js
+41
-8
locale/gitlab.pot
locale/gitlab.pot
+3
-0
No files found.
config/feature_flags/development/vuln_report_new_project_filter.yml
0 → 100644
View file @
ab76aa69
---
name
:
vuln_report_new_project_filter
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55745
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/334380
milestone
:
'
14.2'
type
:
development
group
:
group::threat insights
default_enabled
:
false
ee/app/assets/javascripts/security_dashboard/components/shared/filters/filters_layout.vue
View file @
ab76aa69
<
script
>
import
{
debounce
}
from
'
lodash
'
;
import
{
debounce
,
cloneDeep
,
isEqual
}
from
'
lodash
'
;
import
{
stateFilter
,
severityFilter
,
...
...
@@ -9,12 +9,15 @@ import {
getProjectFilter
,
}
from
'
ee/security_dashboard/helpers
'
;
import
{
DASHBOARD_TYPES
}
from
'
ee/security_dashboard/store/constants
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
ActivityFilter
from
'
./activity_filter.vue
'
;
import
ProjectFilter
from
'
./project_filter.vue
'
;
import
ScannerFilter
from
'
./scanner_filter.vue
'
;
import
SimpleFilter
from
'
./simple_filter.vue
'
;
export
default
{
components
:
{
SimpleFilter
,
ScannerFilter
,
ActivityFilter
},
components
:
{
SimpleFilter
,
ScannerFilter
,
ActivityFilter
,
ProjectFilter
},
mixins
:
[
glFeatureFlagsMixin
()],
inject
:
[
'
dashboardType
'
],
props
:
{
projects
:
{
type
:
Array
,
required
:
false
,
default
:
undefined
},
...
...
@@ -31,8 +34,17 @@ export default {
isPipeline
()
{
return
this
.
dashboardType
===
DASHBOARD_TYPES
.
PIPELINE
;
},
isGroupDashboard
()
{
return
this
.
dashboardType
===
DASHBOARD_TYPES
.
GROUP
;
},
isInstanceDashboard
()
{
return
this
.
dashboardType
===
DASHBOARD_TYPES
.
INSTANCE
;
},
shouldShowProjectFilter
()
{
return
Boolean
(
this
.
projects
?.
length
);
return
this
.
isGroupDashboard
||
this
.
isInstanceDashboard
;
},
shouldShowNewProjectFilter
()
{
return
this
.
glFeatures
.
vulnReportNewProjectFilter
&&
this
.
shouldShowProjectFilter
;
},
projectFilter
()
{
return
getProjectFilter
(
this
.
projects
);
...
...
@@ -40,8 +52,12 @@ export default {
},
methods
:
{
updateFilterQuery
(
query
)
{
const
oldQuery
=
cloneDeep
(
this
.
filterQuery
);
this
.
filterQuery
=
{
...
this
.
filterQuery
,
...
query
};
if
(
!
isEqual
(
oldQuery
,
this
.
filterQuery
))
{
this
.
emitFilterChange
();
}
},
// When this component is first shown, every filter will emit its own @filter-changed event at
// the same time, which will trigger this method multiple times. We'll debounce it so that it
...
...
@@ -86,8 +102,14 @@ export default {
:filter=
"$options.activityFilter"
@
filter-changed=
"updateFilterQuery"
/>
<project-filter
v-if=
"shouldShowNewProjectFilter"
:filter=
"projectFilter"
@
filter-changed=
"updateFilterQuery"
/>
<simple-filter
v-if=
"shouldShowProjectFilter"
v-
else-
if=
"shouldShowProjectFilter"
:filter=
"projectFilter"
:data-testid=
"projectFilter.id"
@
filter-changed=
"updateFilterQuery"
...
...
ee/app/assets/javascripts/security_dashboard/components/shared/filters/project_filter.vue
0 → 100644
View file @
ab76aa69
<
script
>
import
{
GlDropdownDivider
,
GlDropdownText
,
GlLoadingIcon
,
GlSafeHtmlDirective
as
SafeHtml
,
}
from
'
@gitlab/ui
'
;
import
{
escapeRegExp
,
has
,
xorBy
}
from
'
lodash
'
;
import
{
DASHBOARD_TYPES
}
from
'
ee/security_dashboard/store/constants
'
;
import
createFlash
from
'
~/flash
'
;
import
{
convertToGraphQLIds
}
from
'
~/graphql_shared/utils
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
groupProjectsQuery
from
'
../../../graphql/queries/group_projects.query.graphql
'
;
import
instanceProjectsQuery
from
'
../../../graphql/queries/instance_projects.query.graphql
'
;
import
{
mapProjects
,
PROJECT_LOADING_ERROR_MESSAGE
}
from
'
../../../helpers
'
;
import
FilterBody
from
'
./filter_body.vue
'
;
import
FilterItem
from
'
./filter_item.vue
'
;
import
SimpleFilter
from
'
./simple_filter.vue
'
;
const
SEARCH_TERM_MINIMUM_LENGTH
=
3
;
const
SELECTED_PROJECTS_MAX_COUNT
=
100
;
const
PROJECT_ENTITY_NAME
=
'
Project
'
;
const
QUERY_CONFIGS
=
{
[
DASHBOARD_TYPES
.
GROUP
]:
{
query
:
groupProjectsQuery
,
property
:
'
group
'
,
},
[
DASHBOARD_TYPES
.
INSTANCE
]:
{
query
:
instanceProjectsQuery
,
property
:
'
instanceSecurityDashboard
'
,
},
};
export
default
{
components
:
{
FilterBody
,
FilterItem
,
GlDropdownDivider
,
GlLoadingIcon
,
GlDropdownText
,
},
directives
:
{
SafeHtml
},
extends
:
SimpleFilter
,
inject
:
[
'
groupFullPath
'
,
'
dashboardType
'
],
data
:
()
=>
({
projectsCache
:
{},
projects
:
[],
hasDropdownBeenOpened
:
false
,
}),
computed
:
{
options
()
{
// Return the projects that exist.
return
Object
.
values
(
this
.
projectsCache
).
filter
(
Boolean
);
},
selectedSet
()
{
return
new
Set
(
this
.
selectedOptions
.
map
((
x
)
=>
x
.
id
));
},
selectableProjects
()
{
// When searching, we want the "select in place" behavior when a dropdown item is clicked, so
// we show all the projects. If not, we want the "move the selected item to the top" behavior,
// so we show only unselected projects:
return
this
.
isSearching
?
this
.
projects
:
this
.
projects
.
filter
((
x
)
=>
!
this
.
isSelected
(
x
.
id
));
},
isLoadingProjects
()
{
return
this
.
$apollo
.
queries
.
projects
.
loading
;
},
isLoadingProjectsById
()
{
return
this
.
$apollo
.
queries
.
projectsById
.
loading
;
},
isSearchTooShort
()
{
return
this
.
isSearching
&&
this
.
searchTerm
.
length
<
SEARCH_TERM_MINIMUM_LENGTH
;
},
isSearching
()
{
return
this
.
searchTerm
.
length
>
0
;
},
showSelectedProjectsSection
()
{
return
this
.
selectedOptions
?.
length
&&
!
this
.
isSearching
;
},
showAllOption
()
{
return
!
this
.
isLoadingProjects
&&
!
this
.
isSearching
&&
!
this
.
isMaxProjectsSelected
;
},
isMaxProjectsSelected
()
{
return
this
.
selectedOptions
?.
length
>=
SELECTED_PROJECTS_MAX_COUNT
;
},
uncachedIds
()
{
const
ids
=
this
.
querystringIds
.
includes
(
this
.
filter
.
allOption
.
id
)
?
[]
:
this
.
querystringIds
;
return
ids
.
filter
((
id
)
=>
!
has
(
this
.
projectsCache
,
id
));
},
queryConfig
()
{
return
QUERY_CONFIGS
[
this
.
dashboardType
];
},
},
apollo
:
{
// Gets the projects from the project IDs in the querystring and adds them to the cache.
projectsById
:
{
query
()
{
return
this
.
queryConfig
.
query
;
},
manual
:
true
,
variables
()
{
return
{
pageSize
:
20
,
fullPath
:
this
.
groupFullPath
,
// The IDs have to be in the format "gid://gitlab/Project/${projectId}"
ids
:
convertToGraphQLIds
(
PROJECT_ENTITY_NAME
,
this
.
uncachedIds
),
};
},
result
({
data
})
{
// Add an entry to the cache for each uncached ID. We need to do this because the backend
// won't return a record for invalid IDs, so we need to record which IDs were queried for.
this
.
uncachedIds
.
forEach
((
id
)
=>
{
this
.
$set
(
this
.
projectsCache
,
id
,
undefined
);
});
const
property
=
data
[
this
.
queryConfig
.
property
];
const
projects
=
mapProjects
(
property
.
projects
.
nodes
);
this
.
saveProjectsToCache
(
projects
);
// Now that we have the project for each uncached ID, set the selected options.
this
.
selectedOptions
=
this
.
querystringOptions
;
},
error
()
{
createFlash
({
message
:
PROJECT_LOADING_ERROR_MESSAGE
});
},
skip
()
{
// Skip if we've already cached all the projects for every querystring ID.
return
!
this
.
uncachedIds
.
length
;
},
},
// Gets the projects for the group with an optional search, to show as dropdown options.
projects
:
{
query
()
{
return
this
.
queryConfig
.
query
;
},
variables
()
{
return
{
fullPath
:
this
.
groupFullPath
,
search
:
this
.
searchTerm
,
};
},
update
(
data
)
{
const
property
=
data
[
this
.
queryConfig
.
property
];
return
mapProjects
(
property
.
projects
.
nodes
);
},
result
()
{
this
.
saveProjectsToCache
(
this
.
projects
);
},
error
()
{
createFlash
({
message
:
PROJECT_LOADING_ERROR_MESSAGE
});
},
skip
()
{
return
!
this
.
hasDropdownBeenOpened
||
this
.
isSearchTooShort
||
this
.
isMaxProjectsSelected
;
},
},
},
methods
:
{
processQuerystringIds
()
{
if
(
this
.
uncachedIds
.
length
)
{
this
.
emitFilterChanged
({
[
this
.
filter
.
id
]:
this
.
querystringIds
});
}
else
{
this
.
selectedOptions
=
this
.
querystringOptions
;
}
},
toggleOption
(
option
)
{
// Toggle the option's existence in the array.
this
.
selectedOptions
=
xorBy
(
this
.
selectedOptions
,
[
option
],
(
x
)
=>
x
.
id
);
this
.
updateQuerystring
();
},
setDropdownOpened
()
{
this
.
hasDropdownBeenOpened
=
true
;
},
highlightSearchTerm
(
name
)
{
// If we use the regex with no search term, it will wrap every character with
<
b
>
,
i
.
e
.
// '
<
b
>
1
<
/b><b>2</
b
><
b
>
3
<
/b>'
.
if
(
!
this
.
isSearching
)
{
return
name
;
}
// If the search term is "sec rep", split it into "sec|rep" so that a project with the name
// "Security Reports" is highlighted as "SECurity REPorts".
const
terms
=
escapeRegExp
(
this
.
searchTerm
).
split
(
'
'
).
join
(
'
|
'
);
const
regex
=
new
RegExp
(
`(
${
terms
}
)`
,
'
gi
'
);
return
name
.
replace
(
regex
,
'
<b>$1</b>
'
);
},
saveProjectsToCache
(
projects
)
{
projects
.
forEach
((
project
)
=>
this
.
$set
(
this
.
projectsCache
,
project
.
id
,
project
));
},
},
i18n
:
{
enterMoreCharactersToSearch
:
__
(
'
Enter at least three characters to search
'
),
maxProjectsSelected
:
s__
(
'
SecurityReports|Maximum selected projects limit reached
'
),
noMatchingResults
:
__
(
'
No matching results
'
),
},
};
</
script
>
<
template
>
<filter-body
v-model.trim=
"searchTerm"
:name=
"filter.name"
:selected-options=
"selectedOptionsOrAll"
:show-search-box=
"true"
:loading=
"isLoadingProjectsById"
@
dropdown-show=
"setDropdownOpened"
>
<div
v-if=
"showSelectedProjectsSection"
data-testid=
"selected-projects-section"
>
<filter-item
v-for=
"project in selectedOptions"
:key=
"project.id"
:is-checked=
"true"
:text=
"project.name"
@
click=
"toggleOption(project)"
/>
<gl-dropdown-divider
/>
</div>
<filter-item
v-if=
"showAllOption"
:is-checked=
"isNoOptionsSelected"
:text=
"filter.allOption.name"
data-testid=
"allOption"
@
click=
"deselectAllOptions"
/>
<gl-loading-icon
v-if=
"isLoadingProjects"
size=
"md"
class=
"gl-mt-4 gl-mb-3"
/>
<gl-dropdown-text
v-else-if=
"isMaxProjectsSelected"
>
{{
$options
.
i18n
.
maxProjectsSelected
}}
</gl-dropdown-text>
<gl-dropdown-text
v-else-if=
"isSearchTooShort"
>
{{
$options
.
i18n
.
enterMoreCharactersToSearch
}}
</gl-dropdown-text>
<gl-dropdown-text
v-else-if=
"!projects.length"
>
{{
$options
.
i18n
.
noMatchingResults
}}
</gl-dropdown-text>
<template
v-else
>
<filter-item
v-for=
"project in selectableProjects"
:key=
"project.id"
:is-checked=
"isSelected(project.id)"
@
click=
"toggleOption(project)"
>
<div
v-safe-html=
"highlightSearchTerm(project.name)"
class=
"gl-text-truncate"
></div>
</filter-item>
</
template
>
</filter-body>
</template>
ee/app/assets/javascripts/security_dashboard/components/shared/filters/simple_filter.vue
View file @
ab76aa69
...
...
@@ -36,10 +36,10 @@ export default {
return
new
Set
(
this
.
selectedOptions
);
},
isNoOptionsSelected
()
{
return
this
.
selectedOptions
.
length
<=
0
;
return
this
.
selectedOptions
?
.
length
<=
0
;
},
selectedOptionsOrAll
()
{
return
this
.
selectedOptions
.
length
?
this
.
selectedOptions
:
[
this
.
filter
.
allOption
];
return
this
.
selectedOptions
?
.
length
?
this
.
selectedOptions
:
[
this
.
filter
.
allOption
];
},
filterObject
()
{
// This is passed to the vulnerability list's GraphQL query as a variable.
...
...
@@ -52,7 +52,9 @@ export default {
},
querystringIds
()
{
const
ids
=
this
.
$route
?.
query
[
this
.
filter
.
id
]
||
[];
return
Array
.
isArray
(
ids
)
?
ids
:
[
ids
];
const
idArray
=
Array
.
isArray
(
ids
)
?
ids
:
[
ids
];
return
idArray
.
sort
();
},
querystringOptions
()
{
// If the querystring IDs includes the All option, return an empty array. We'll do this even
...
...
@@ -75,15 +77,13 @@ export default {
},
watch
:
{
selectedOptions
()
{
this
.
$emit
(
'
filter-changed
'
,
this
.
filterObject
);
this
.
emitFilterChanged
(
this
.
filterObject
);
},
},
created
()
{
this
.
selectedOptions
=
this
.
querystringOptions
;
this
.
processQuerystringIds
()
;
// When the user clicks the forward/back browser buttons, update the selected options.
window
.
addEventListener
(
'
popstate
'
,
()
=>
{
this
.
selectedOptions
=
this
.
querystringOptions
;
});
window
.
addEventListener
(
'
popstate
'
,
this
.
processQuerystringIds
);
},
methods
:
{
toggleOption
(
option
)
{
...
...
@@ -108,6 +108,12 @@ export default {
isSelected
(
option
)
{
return
this
.
selectedSet
.
has
(
option
);
},
processQuerystringIds
()
{
this
.
selectedOptions
=
this
.
querystringOptions
;
},
emitFilterChanged
(
data
)
{
this
.
$emit
(
'
filter-changed
'
,
data
);
},
},
};
</
script
>
...
...
ee/app/assets/javascripts/security_dashboard/graphql/queries/group_projects.query.graphql
View file @
ab76aa69
query
groupProjects
(
$fullPath
:
ID
!)
{
query
groupProjects
(
$fullPath
:
ID
!
,
$ids
:
[
ID
!],
$search
:
String
,
$pageSize
:
Int
)
{
group
(
fullPath
:
$fullPath
)
{
projects
(
includeSubgroups
:
true
)
{
projects
(
includeSubgroups
:
true
,
ids
:
$ids
,
search
:
$search
,
first
:
$pageSize
)
{
nodes
{
id
name
...
...
ee/app/assets/javascripts/security_dashboard/graphql/queries/instance_projects.query.graphql
View file @
ab76aa69
query
instanceProjects
{
query
instanceProjects
(
$search
:
String
)
{
instanceSecurityDashboard
{
projects
{
projects
(
search
:
$search
)
{
nodes
{
id
name
...
...
ee/app/assets/javascripts/security_dashboard/helpers.js
View file @
ab76aa69
...
...
@@ -3,6 +3,7 @@ import { REPORT_TYPES, SEVERITY_LEVELS } from 'ee/security_dashboard/store/const
import
{
BASE_FILTERS
}
from
'
ee/security_dashboard/store/modules/filters/constants
'
;
import
convertReportType
from
'
ee/vue_shared/security_reports/store/utils/convert_report_type
'
;
import
{
VULNERABILITY_STATES
}
from
'
ee/vulnerabilities/constants
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
convertObjectPropsToSnakeCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
DEFAULT_SCANNER
}
from
'
./constants
'
;
...
...
@@ -10,8 +11,8 @@ import { DEFAULT_SCANNER } from './constants';
const
parseOptions
=
(
obj
)
=>
Object
.
entries
(
obj
).
map
(([
id
,
name
])
=>
({
id
:
id
.
toUpperCase
(),
name
}));
export
const
mapProjects
=
(
projects
)
=>
projects
.
map
((
p
)
=>
({
id
:
p
.
id
.
split
(
'
/
'
).
pop
(),
name
:
p
.
name
}));
export
const
mapProjects
=
(
projects
=
[]
)
=>
projects
.
map
((
p
)
=>
({
id
:
getIdFromGraphQLId
(
p
.
id
).
toString
(),
name
:
p
.
name
}));
const
stateOptions
=
parseOptions
(
VULNERABILITY_STATES
);
const
defaultStateOptions
=
stateOptions
.
filter
((
x
)
=>
[
'
DETECTED
'
,
'
CONFIRMED
'
].
includes
(
x
.
id
));
...
...
ee/app/controllers/groups/security/vulnerabilities_controller.rb
View file @
ab76aa69
...
...
@@ -7,6 +7,7 @@ module Groups
before_action
do
push_frontend_feature_flag
(
:vulnerability_management_survey
,
type: :ops
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:vuln_report_new_project_filter
,
current_user
,
default_enabled: :yaml
)
end
feature_category
:vulnerability_management
...
...
ee/app/controllers/security/vulnerabilities_controller.rb
View file @
ab76aa69
...
...
@@ -6,6 +6,7 @@ module Security
before_action
do
push_frontend_feature_flag
(
:vulnerability_management_survey
,
type: :ops
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:vuln_report_new_project_filter
,
current_user
,
default_enabled: :yaml
)
end
end
end
ee/spec/frontend/security_dashboard/components/shared/filters/filters_layout_spec.js
View file @
ab76aa69
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
ActivityFilter
from
'
ee/security_dashboard/components/shared/filters/activity_filter.vue
'
;
import
Filters
from
'
ee/security_dashboard/components/shared/filters/filters_layout.vue
'
;
import
ProjectFilter
from
'
ee/security_dashboard/components/shared/filters/project_filter.vue
'
;
import
ScannerFilter
from
'
ee/security_dashboard/components/shared/filters/scanner_filter.vue
'
;
import
SimpleFilter
from
'
ee/security_dashboard/components/shared/filters/simple_filter.vue
'
;
import
{
getProjectFilter
,
simpleScannerFilter
}
from
'
ee/security_dashboard/helpers
'
;
...
...
@@ -20,6 +21,7 @@ describe('First class vulnerability filters component', () => {
const
findVendorScannerFilter
=
()
=>
wrapper
.
findComponent
(
ScannerFilter
);
const
findActivityFilter
=
()
=>
wrapper
.
findComponent
(
ActivityFilter
);
const
findProjectFilter
=
()
=>
wrapper
.
findByTestId
(
getProjectFilter
([]).
id
);
const
findNewProjectFilter
=
()
=>
wrapper
.
findComponent
(
ProjectFilter
);
const
createComponent
=
({
props
,
provide
}
=
{})
=>
{
return
extendedWrapper
(
...
...
@@ -56,21 +58,52 @@ describe('First class vulnerability filters component', () => {
});
});
describe
(
'
when project filter is populated dynamically
'
,
()
=>
{
it
(
'
should not render the project filter if there are no options
'
,
async
()
=>
{
wrapper
=
createComponent
({
props
:
{
projects
:
[]
}
});
describe
(
'
project filter
'
,
()
=>
{
it
.
each
`
dashboardType | isShown
${
DASHBOARD_TYPES
.
PROJECT
}
|
${
false
}
${
DASHBOARD_TYPES
.
PIPELINE
}
|
${
false
}
${
DASHBOARD_TYPES
.
GROUP
}
|
${
true
}
${
DASHBOARD_TYPES
.
INSTANCE
}
|
${
true
}
`
(
'
on the $dashboardType report the project filter shown is $isShown
'
,
({
dashboardType
,
isShown
})
=>
{
wrapper
=
createComponent
({
provide
:
{
dashboardType
}
});
expect
(
findProjectFilter
().
exists
()).
toBe
(
false
);
});
expect
(
findProjectFilter
().
exists
()).
toBe
(
isShown
);
},
);
it
(
'
should render the project filter with the expected options
'
,
async
()
=>
{
wrapper
=
createComponent
({
props
:
{
projects
}
});
it
(
'
should render the project filter with the expected options
'
,
()
=>
{
wrapper
=
createComponent
({
provide
:
{
dashboardType
:
DASHBOARD_TYPES
.
GROUP
},
props
:
{
projects
},
});
expect
(
findProjectFilter
().
props
(
'
filter
'
).
options
).
toEqual
([
{
id
:
'
11
'
,
name
:
projects
[
0
].
name
},
{
id
:
'
12
'
,
name
:
projects
[
1
].
name
},
]);
});
it
.
each
`
featureFlag | isProjectFilterShown | isNewProjectFilterShown
${
false
}
|
${
true
}
|
${
false
}
${
true
}
|
${
false
}
|
${
true
}
`
(
'
should show the correct project filter when vulnReportNewProjectFilter feature flag is $featureFlag
'
,
({
featureFlag
,
isProjectFilterShown
,
isNewProjectFilterShown
})
=>
{
wrapper
=
createComponent
({
provide
:
{
dashboardType
:
DASHBOARD_TYPES
.
GROUP
,
glFeatures
:
{
vulnReportNewProjectFilter
:
featureFlag
},
},
});
expect
(
findProjectFilter
().
exists
()).
toBe
(
isProjectFilterShown
);
expect
(
findNewProjectFilter
().
exists
()).
toBe
(
isNewProjectFilterShown
);
},
);
});
describe
(
'
activity filter
'
,
()
=>
{
...
...
locale/gitlab.pot
View file @
ab76aa69
...
...
@@ -29186,6 +29186,9 @@ msgstr ""
msgid "SecurityReports|Manage and track vulnerabilities identified in your selected projects. Vulnerabilities for selected projects with security testing configured are shown here."
msgstr ""
msgid "SecurityReports|Maximum selected projects limit reached"
msgstr ""
msgid "SecurityReports|Monitor vulnerabilities in all of your projects"
msgstr ""
...
...
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