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
33051a37
Commit
33051a37
authored
Apr 08, 2021
by
David Pisek
Committed by
Enrique Alcántara
Apr 08, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add recursive list component to generic reports section on vulnerability details page
parent
6d4a29e9
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
267 additions
and
15 deletions
+267
-15
ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_item.vue
...vulnerabilities/components/generic_report/report_item.vue
+2
-0
ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_section.vue
...nerabilities/components/generic_report/report_section.vue
+9
-2
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/constants.js
...nerabilities/components/generic_report/types/constants.js
+2
-1
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/list.vue
.../vulnerabilities/components/generic_report/types/list.vue
+33
-0
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/url.vue
...s/vulnerabilities/components/generic_report/types/url.vue
+1
-0
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/utils.js
.../vulnerabilities/components/generic_report/types/utils.js
+89
-4
ee/app/assets/stylesheets/page_bundles/security_dashboard.scss
...p/assets/stylesheets/page_bundles/security_dashboard.scss
+13
-0
ee/changelogs/unreleased/324891-fe-generic-report-schema-render-named-list-list-url-types-on-vulne.yml
...port-schema-render-named-list-list-url-types-on-vulne.yml
+5
-0
ee/spec/frontend/vulnerabilities/generic_report/report_item_spec.js
...ontend/vulnerabilities/generic_report/report_item_spec.js
+4
-0
ee/spec/frontend/vulnerabilities/generic_report/types/list_spec.js
...rontend/vulnerabilities/generic_report/types/list_spec.js
+38
-0
ee/spec/frontend/vulnerabilities/generic_report/types/utils_spec.js
...ontend/vulnerabilities/generic_report/types/utils_spec.js
+71
-8
No files found.
ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_item.vue
View file @
33051a37
<
script
>
import
List
from
'
./types/list.vue
'
;
import
Url
from
'
./types/url.vue
'
;
export
default
{
components
:
{
List
,
Url
,
},
props
:
{
...
...
ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_section.vue
View file @
33051a37
...
...
@@ -3,7 +3,9 @@ import { GlCollapse, GlIcon } from '@gitlab/ui';
import
{
s__
}
from
'
~/locale
'
;
import
ReportItem
from
'
./report_item.vue
'
;
import
ReportRow
from
'
./report_row.vue
'
;
import
{
isValidReportType
}
from
'
./types/utils
'
;
import
{
filterTypesAndLimitListDepth
}
from
'
./types/utils
'
;
const
NESTED_LISTS_MAX_DEPTH
=
4
;
export
default
{
i18n
:
{
...
...
@@ -27,8 +29,13 @@ export default {
};
},
computed
:
{
filteredDetails
()
{
return
filterTypesAndLimitListDepth
(
this
.
details
,
{
maxDepth
:
NESTED_LISTS_MAX_DEPTH
,
});
},
detailsEntries
()
{
return
Object
.
entries
(
this
.
details
).
filter
(([,
item
])
=>
isValidReportType
(
item
.
type
)
);
return
Object
.
entries
(
this
.
filteredDetails
);
},
hasDetails
()
{
return
this
.
detailsEntries
.
length
>
0
;
...
...
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/constants.js
View file @
33051a37
export
const
REPORT_TYPE_LIST
=
'
list
'
;
export
const
REPORT_TYPE_URL
=
'
url
'
;
export
const
REPORT_TYPES
=
[
REPORT_TYPE_URL
];
export
const
REPORT_TYPES
=
[
REPORT_TYPE_
LIST
,
REPORT_TYPE_
URL
];
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/list.vue
0 → 100644
View file @
33051a37
<
script
>
import
{
isListType
}
from
'
./utils
'
;
export
default
{
isListType
,
components
:
{
ReportItem
:
()
=>
import
(
'
../report_item.vue
'
),
},
inheritAttrs
:
false
,
props
:
{
items
:
{
type
:
Array
,
required
:
true
,
},
},
computed
:
{
hasNestedListItems
()
{
return
this
.
items
.
some
(
isListType
);
},
},
};
</
script
>
<
template
>
<ul
class=
"generic-report-list"
:class=
"
{ 'generic-report-list-nested': hasNestedListItems }">
<li
v-for=
"item in items"
:key=
"item.name"
:class=
"
{ 'gl-list-style-none!': $options.isListType(item) }"
>
<report-item
:item=
"item"
/>
</li>
</ul>
</
template
>
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/url.vue
View file @
33051a37
...
...
@@ -5,6 +5,7 @@ export default {
components
:
{
GlLink
,
},
inheritAttrs
:
false
,
props
:
{
href
:
{
type
:
String
,
...
...
ee/app/assets/javascripts/vulnerabilities/components/generic_report/types/utils.js
View file @
33051a37
import
{
REPORT_TYPES
}
from
'
./constants
'
;
import
{
overEvery
}
from
'
lodash
'
;
import
{
REPORT_TYPES
,
REPORT_TYPE_LIST
}
from
'
./constants
'
;
/**
* Check if
a given type is support
ed (i.e, is mapped to a component and can be rendered)
* Check if
the given report is of a type that can be render
ed (i.e, is mapped to a component and can be rendered)
*
* @param
string type
* @param
{{ type: string }} reportItem
* @returns boolean
*/
export
const
isValidReportType
=
(
type
)
=>
REPORT_TYPES
.
includes
(
type
);
const
isSupportedType
=
({
type
})
=>
REPORT_TYPES
.
includes
(
type
);
/**
* Check if the given report is of type list
*
* @param {{ type: string } } reportItem
* @returns boolean
*/
export
const
isListType
=
({
type
})
=>
type
===
REPORT_TYPE_LIST
;
/**
* Check if the current report item is of that list and is not nested deeper than the maximum depth
*
* @param {number} maxDepth
* @returns {function}
*/
const
isNotListTypeDeeperThan
=
(
maxDepth
)
=>
(
item
,
currentDepth
)
=>
{
return
!
isListType
(
item
)
||
maxDepth
>
currentDepth
+
1
;
};
/**
* Takes an array of report items and recursively filters out items not matching the given condition
*
* @param {array} items
* @param {{condition: function, currentDepth? : number }} options
* @returns {array}
*/
const
deepFilterListItems
=
(
items
,
{
condition
,
currentDepth
=
0
})
=>
items
.
reduce
((
filteredItems
,
currentItem
)
=>
{
const
shouldInsertItem
=
condition
(
currentItem
,
currentDepth
);
if
(
!
shouldInsertItem
)
{
return
filteredItems
;
}
const
nextItem
=
{
...
currentItem
};
if
(
isListType
(
nextItem
))
{
nextItem
.
items
=
deepFilterListItems
(
currentItem
.
items
,
{
condition
,
currentDepth
:
currentDepth
+
1
,
});
}
return
[...
filteredItems
,
nextItem
];
},
[]);
/**
* If the given entry is a list it will deep filter it's child items based on the given condition
*
* @param {function} condition
* @returns {{*}}
*/
const
filterNestedListsItems
=
(
condition
)
=>
([
label
,
reportItem
])
=>
{
const
filtered
=
isListType
(
reportItem
)
?
{
...
reportItem
,
items
:
deepFilterListItems
(
reportItem
.
items
,
{
condition
}),
}
:
reportItem
;
return
[
label
,
filtered
];
};
/**
* Takes a vulnerabilities details object - containing generic report data
* Returns a copy of the report data with the following items being filtered:
*
* 1.) Report items which have a type that is not supported for rendering
* 2.) Nested list items, which are nested beyond the given maximum depth
*
* @param {object} entries
* @param {{ maxDepth?: number }} options
* @returns {object}
*/
export
const
filterTypesAndLimitListDepth
=
(
data
,
{
maxDepth
=
5
}
=
{})
=>
{
const
entries
=
Object
.
entries
(
data
);
const
filterCriteria
=
overEvery
([
isSupportedType
,
isNotListTypeDeeperThan
(
maxDepth
)]);
const
filteredEntries
=
entries
.
filter
(([,
reportItem
])
=>
isSupportedType
(
reportItem
))
.
map
(
filterNestedListsItems
(
filterCriteria
));
return
Object
.
fromEntries
(
filteredEntries
);
};
ee/app/assets/stylesheets/page_bundles/security_dashboard.scss
View file @
33051a37
...
...
@@ -128,3 +128,16 @@ $selection-summary-with-error-height: 118px;
@include
gl-border-b-0
;
}
}
.generic-report-list
{
li
{
@include
gl-ml-0
;
@include
gl-list-style-none
;
}
&
.generic-report-list-nested
li
{
@include
gl-ml-5
;
list-style-type
:
disc
;
}
}
ee/changelogs/unreleased/324891-fe-generic-report-schema-render-named-list-list-url-types-on-vulne.yml
0 → 100644
View file @
33051a37
---
title
:
Add recursive list rendering to generic vulnerability reports
merge_request
:
58206
author
:
type
:
added
ee/spec/frontend/vulnerabilities/generic_report/report_item_spec.js
View file @
33051a37
...
...
@@ -3,6 +3,7 @@ import ReportItem from 'ee/vulnerabilities/components/generic_report/report_item
import
{
REPORT_TYPES
,
REPORT_TYPE_URL
,
REPORT_TYPE_LIST
,
}
from
'
ee/vulnerabilities/components/generic_report/types/constants
'
;
import
{
extendedWrapper
}
from
'
helpers/vue_test_utils_helper
'
;
...
...
@@ -10,6 +11,9 @@ const TEST_DATA = {
[
REPORT_TYPE_URL
]:
{
href
:
'
http://foo.com
'
,
},
[
REPORT_TYPE_LIST
]:
{
items
:
[{
type
:
'
foo
'
}],
},
};
describe
(
'
ee/vulnerabilities/components/generic_report/report_item.vue
'
,
()
=>
{
...
...
ee/spec/frontend/vulnerabilities/generic_report/types/list_spec.js
0 → 100644
View file @
33051a37
import
{
screen
}
from
'
@testing-library/dom
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
ReportItem
from
'
ee/vulnerabilities/components/generic_report/report_item.vue
'
;
import
List
from
'
ee/vulnerabilities/components/generic_report/types/list.vue
'
;
const
TEST_DATA
=
{
items
:
[
{
type
:
'
url
'
,
href
:
'
http://foo.bar
'
},
{
type
:
'
url
'
,
href
:
'
http://bar.baz
'
},
],
};
describe
(
'
ee/vulnerabilities/components/generic_report/types/list.vue
'
,
()
=>
{
let
wrapper
;
const
createWrapper
=
()
=>
{
return
shallowMount
(
List
,
{
propsData
:
{
items
:
TEST_DATA
.
items
,
},
attachTo
:
document
.
body
,
});
};
const
findReportItems
=
()
=>
wrapper
.
findAllComponents
(
ReportItem
);
beforeEach
(()
=>
{
wrapper
=
createWrapper
();
});
it
(
'
renders a list
'
,
()
=>
{
expect
(
screen
.
getByRole
(
'
list
'
)).
toBeInstanceOf
(
HTMLElement
);
});
it
(
'
renders a report-item for each item
'
,
()
=>
{
expect
(
findReportItems
()).
toHaveLength
(
TEST_DATA
.
items
.
length
);
});
});
ee/spec/frontend/vulnerabilities/generic_report/types/utils_spec.js
View file @
33051a37
import
{
REPORT_TYPES
}
from
'
ee/vulnerabilities/components/generic_report/types/constants
'
;
import
{
isValidReportType
}
from
'
ee/vulnerabilities/components/generic_report/types/utils
'
;
import
{
REPORT_TYPE_LIST
,
REPORT_TYPE_URL
,
}
from
'
ee/vulnerabilities/components/generic_report/types/constants
'
;
import
{
filterTypesAndLimitListDepth
}
from
'
ee/vulnerabilities/components/generic_report/types/utils
'
;
const
MOCK_REPORT_TYPE_UNSUPPORTED
=
'
MOCK_REPORT_TYPE_UNSUPPORTED
'
;
const
TEST_DATA
=
{
url
:
{
type
:
REPORT_TYPE_URL
,
name
:
'
url1
'
,
},
list
:
{
type
:
REPORT_TYPE_LIST
,
name
:
'
rootList
'
,
items
:
[
{
type
:
REPORT_TYPE_URL
,
name
:
'
url2
'
},
{
type
:
REPORT_TYPE_LIST
,
name
:
'
listDepthOne
'
,
items
:
[
{
type
:
REPORT_TYPE_URL
,
name
:
'
url3
'
},
{
type
:
REPORT_TYPE_LIST
,
name
:
'
listDepthTwo
'
,
items
:
[
{
type
:
REPORT_TYPE_URL
,
name
:
'
url4
'
},
{
type
:
REPORT_TYPE_LIST
,
name
:
'
listDepthThree
'
,
items
:
[
{
type
:
REPORT_TYPE_URL
,
name
:
'
url5
'
},
{
type
:
MOCK_REPORT_TYPE_UNSUPPORTED
},
],
},
{
type
:
MOCK_REPORT_TYPE_UNSUPPORTED
},
],
},
{
type
:
MOCK_REPORT_TYPE_UNSUPPORTED
},
],
},
{
type
:
MOCK_REPORT_TYPE_UNSUPPORTED
},
],
},
};
describe
(
'
ee/vulnerabilities/components/generic_report/types/utils
'
,
()
=>
{
describe
(
'
isValidReportType
'
,
()
=>
{
it
.
each
(
REPORT_TYPES
)(
'
returns "true" if the given type is a "%s"
'
,
(
reportType
)
=>
{
expect
(
isValidReportType
(
reportType
)).
toBe
(
true
);
});
describe
(
'
filterTypesAndLimitListDepth
'
,
()
=>
{
const
getRootList
=
(
reportsData
)
=>
reportsData
.
list
;
const
getListWithDepthOne
=
(
reportsData
)
=>
reportsData
.
list
.
items
[
1
];
const
getListWithDepthTwo
=
(
reportsData
)
=>
reportsData
.
list
.
items
[
1
].
items
[
1
];
const
includesType
=
(
type
)
=>
(
items
)
=>
items
.
find
(({
type
:
currentType
})
=>
currentType
===
type
)
!==
undefined
;
const
includesListItem
=
includesType
(
REPORT_TYPE_LIST
);
const
includesUnsupportedType
=
includesType
(
MOCK_REPORT_TYPE_UNSUPPORTED
);
describe
.
each
`
depth | getListAtCurrentDepth
${
1
}
|
${
getRootList
}
${
2
}
|
${
getListWithDepthOne
}
${
3
}
|
${
getListWithDepthTwo
}
`
(
'
with nested lists at depth: "$depth"
'
,
({
depth
,
getListAtCurrentDepth
})
=>
{
const
filteredData
=
filterTypesAndLimitListDepth
(
TEST_DATA
,
{
maxDepth
:
depth
});
it
(
'
filters list items
'
,
()
=>
{
expect
(
includesListItem
(
getListAtCurrentDepth
(
TEST_DATA
).
items
)).
toBe
(
true
);
expect
(
includesListItem
(
getListAtCurrentDepth
(
filteredData
).
items
)).
toBe
(
false
);
});
it
(
'
returns "false" if the given type is not supported
'
,
()
=>
{
expect
(
isValidReportType
(
'
this-type-does-not-exist
'
)).
toBe
(
false
);
it
(
'
filters items with types that are not supported
'
,
()
=>
{
expect
(
includesUnsupportedType
(
getListAtCurrentDepth
(
TEST_DATA
).
items
)).
toBe
(
true
);
expect
(
includesUnsupportedType
(
getListAtCurrentDepth
(
filteredData
).
items
)).
toBe
(
false
);
});
});
});
});
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