-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Provide CWE Selector #523
Conversation
50bf572
to
2c14d8c
Compare
800da99
to
4340a62
Compare
49c5269
to
7fd876d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to fix the event handler, other comments are minor changes
|
||
onMounted(() => { | ||
loadCweData(); | ||
window.addEventListener('keydown', handleKeyDown); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add the event to the window
instead of the DOM node? just use @keydown
on the element like we do with @click
.
Also if we add an eventListener manually we need to take care of removing it, since it is added to the window the event is not destroyed even if the component is unmounted, so the event is always listening, this is a memory leak, also, each time the component is mounted a new event is added without removing the old one, so it will trigger multiple times.
See this playground as an example https://play.vuejs.org/
In case this is needed we could remove it like this:
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the event is on window
even if I'm not focus on it, it will trigger the handler
Screencast.From.2025-01-27.10-12-13.mp4
import type { CWEMemberType } from '@/types/mitreCwe'; | ||
|
||
const props = withDefaults(defineProps<{ | ||
error?: null | string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we do this everywhere, but it is needed? ZodError
won't be null as far as I know, is either defined or undefined
error?: null | string; | |
error?: string; |
const selectedIndex = ref(-1); | ||
|
||
const loadCweData = () => { | ||
const data = localStorage.getItem('CWE:API-DATA'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should export the constant in the service and use it instead of this magic string
osim/src/services/CweService.ts
Line 4 in 7fd876d
const DATA_KEY = 'CWE:API-DATA'; |
class="item gap-1" | ||
:class="{'selected': index === selectedIndex }" | ||
style="display: flex; justify-content: space-between;" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use bootstrap classes for this
class="item gap-1" | |
:class="{'selected': index === selectedIndex }" | |
style="display: flex; justify-content: space-between;" | |
class="item gap-1 d-flex justify-content-between" | |
:class="{'selected': index === selectedIndex }" |
> | ||
<template v-if="suggestions.length > 0" #suggestions="{ abort }"> | ||
<div class="dropdown-header"> | ||
<span style="flex: 1;">ID</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if we should use thestyle
attribute when we also have custom css.
Bootstrap provides flex-grow-X
and flex-shrink-X
classes, but they are a bit verbose, maybe we could use a class flex-1
and flex-3
and use that instead, that way if the style needs to change only the class needs to be updated and not each component individually
<LabelDiv :label="props.label" class="mb-2"> | ||
<EditableTextWithSuggestions | ||
v-model="modelValue" | ||
:error="props.error" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, no need for props.
:error="props.error" | |
:error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the tests make use of the vm
attribute to access internal state, but none of them tests actual component behavior, the functions may work but how do we know they are triggered when a user clicks something or enter text in the searchbox?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I have been trying to pay closer attention to the tests I write testing the UI and not the business logic of a component. That said, composables make sense to test for business logic though.
import { mountWithConfig } from '@/__tests__/helpers'; | ||
|
||
describe('cweSelector.vue', () => { | ||
let wrapper: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it at least a VueWrapper
so the exists
, html
and vm
properties work.
let wrapper: any; | |
let wrapper: VueWrapper<any>; |
global: { | ||
components: { | ||
EditableTextWithSuggestions, | ||
LabelDiv, | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this necessary? we are not mocking those components
const cweView = await fetchCweView(baseUrl); | ||
const cweCategories = await fetchCweCategories(baseUrl, cweView); | ||
const cweWeaknesses = await fetchCweWeaknesses(baseUrl, cweCategories); | ||
localStorage.setItem(DATA_KEY, JSON.stringify(cweWeaknesses)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the user already has the CWE:API-DATA
localStorage object, all these new properties will not exists until the version of mitre is updated, since we haven't published the cwe selector to prod I don't think we should handle it, just keep in mind to tell the users to clean the localStorage if they are testing this, as some of it could fail
Actually it fails, the
getUsageClass
from CweSelector fill throw an error becauseusage
is undefined and the call to.toLowerCase()
fails
7fd876d
to
fd43cd4
Compare
fd43cd4
to
a200533
Compare
class="item gap-1 d-flex justify-content-between" | ||
:class="{'selected': index === selectedIndex }" | ||
style="justify-content: space-between;" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You added the class but forgot to remove the style
class="item gap-1 d-flex justify-content-between" | |
:class="{'selected': index === selectedIndex }" | |
style="justify-content: space-between;" | |
class="item gap-1 d-flex justify-content-between" | |
:class="{'selected': index === selectedIndex }" | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some suggestions, and a couple of minor change requests
label?: string; | ||
}>(), { | ||
label: '', | ||
error: undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're sort of split between usage of null
and undefined
in the codebase, but maybe undefined
is better as a props default since the null
values should come from the errors
in useFlawModel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I like what you have here better than the default null
s we have elsewhere
const loadCweData = () => { | ||
const data = localStorage.getItem(DATA_KEY); | ||
if (data) { | ||
cweData.value = JSON.parse(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Safest to wrap this in a try/catch block
modelValue.value = queryParts.join(''); | ||
queryRef.value = modelValue.value; | ||
suggestions.value = []; | ||
nextTick(fn); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this to defer/delay the click event until the query has been parsed into a suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not exactly. That is to ensure the suggestions and query have been updated in the DOM before the optional callback function (fn
) is processed.
For example in the scenario of triggering the abort
action (pressing ESC) just after clicking a suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, would you mind adding a comment to that effect (waiting for 'abort'/'commit' events)?
onMounted(() => { | ||
loadCweData(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onMounted(() => { | |
loadCweData(); | |
}); | |
onMounted(loadCweData); |
<span class="flex-3">{{ `${cwe.name}. ` }}</span> | ||
<span class="badge flex-1" :class="getUsageClass(cwe.usage)">{{ `${cwe.usage}` }}</span> | ||
<div class="flex-1"> | ||
<i v-show="cwe.summary != ''" class="icon bi-info-circle" :title="cwe.summary" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<i v-show="cwe.summary != ''" class="icon bi-info-circle" :title="cwe.summary" /> | |
<i v-show="cwe.summary !== ''" class="icon bi-info-circle" :title="cwe.summary" /> |
unless this is what you want, then I think this would be equivalent:
<i v-show="cwe.summary != ''" class="icon bi-info-circle" :title="cwe.summary" /> | |
<i v-show="cwe.summary" class="icon bi-info-circle" :title="cwe.summary" /> |
it('loads data from localStorage', () => { | ||
const data = JSON.stringify([{ id: '123', name: 'Test CWE', status: 'Draft', summary: '', usage: '' }]); | ||
localStorage.setItem(DATA_KEY, data); | ||
wrapper.vm.loadCweData(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be better to mock loadCweData
here or use a localstorage mock, then test something in the UI showing up as a result, otherwise we're effectively testing Vue internals and JavaScript LocalStorage
a200533
to
8099f61
Compare
8099f61
to
a94b0e9
Compare
a94b0e9
to
d51bea1
Compare
OSIDB-3743 CWE Selector UI
Checklist:
Summary:
Update for the OSIM UI to provide a CWE Selector using suggestions to help users.
Corrections in the Mitre service logic
Changes:
CweService.ts
to use the proper data for weaknessesCweSelector
component to be used in the flaw formConsiderations:
Closes OSIDB-3743