Skip to content

Commit d5e2c4a

Browse files
lsongsusexingzhang-suse
authored andcommitted
Change Advisory Vendor interaction from "hover" to "click"
1 parent bc16878 commit d5e2c4a

File tree

2 files changed

+78
-37
lines changed

2 files changed

+78
-37
lines changed

pkg/sbomscanner/components/CveDetails.vue

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ export default {
1313
PRODUCT_NAME,
1414
RESOURCE,
1515
PAGE,
16-
cveDetail: null,
17-
hoverVendor: null,
18-
inside: false,
16+
cveDetail: null,
17+
selectedVendor: null,
1918
};
2019
},
2120
@@ -139,8 +138,15 @@ export default {
139138
link: isNvd ? `${ NVD_BASE_URL }${ cveId }` : ''
140139
};
141140
});
142-
}
141+
},
143142
143+
toggleVendor(vendor) {
144+
if (this.selectedVendor === vendor) {
145+
this.selectedVendor = null;
146+
} else {
147+
this.selectedVendor = vendor;
148+
}
149+
},
144150
},
145151
146152
watch: {
@@ -220,30 +226,28 @@ export default {
220226
<span class="label"> Advisory vendors </span>
221227
<span class="value">{{ cveDetail?.advisoryVendors?.length || t('imageScanner.general.unknown') }}</span>
222228
</div>
223-
<div
224-
class="vendor-tags-wrapper"
225-
@mouseenter="inside = true"
226-
@mouseleave="inside = false; hoverVendor = null"
227-
>
229+
<div class="vendor-tags-wrapper">
228230
<div class="vendor-tags">
229231
<span
230232
v-for="(vendor, index) in cveDetail?.advisoryVendors"
231233
:key="index"
232234
class="vendor-tag"
233-
@mouseenter="hoverVendor = vendor"
235+
:class="{ 'active': selectedVendor === vendor}"
236+
@click="toggleVendor(vendor)"
234237
>
235238
{{ vendor.name }}
236239
</span>
237240
</div>
238-
239-
<!-- Hover panel (always aligned with first tag) -->
240241
<div
241-
v-if="hoverVendor && inside"
242+
v-if="selectedVendor"
242243
class="hover-panel"
243244
>
244-
<h4>References for {{ cveDetail?.id }}</h4>
245+
<div class="panel-header">
246+
<h4>References for {{ cveDetail?.id }}</h4>
247+
<i class="icon icon-close" @click.stop="selectedVendor = null"></i>
248+
</div>
245249
<div
246-
v-for="(ref, rIndex) in hoverVendor.references"
250+
v-for="(ref, rIndex) in selectedVendor?.references"
247251
:key="rIndex"
248252
class="ref-item"
249253
>
@@ -288,13 +292,11 @@ export default {
288292
}
289293
290294
.about {
291-
/* layout */
292295
display: flex;
293296
padding-bottom: 20px;
294297
flex-direction: column;
295298
align-items: flex-start;
296299
align-self: stretch;
297-
/* style */
298300
border-bottom: dashed var(--border-width) var(--input-border);
299301
}
300302
@@ -397,14 +399,16 @@ export default {
397399
border: solid var(--border-width) var(--input-border);
398400
border-radius: 4px;
399401
cursor: pointer;
402+
user-select: none;
400403
}
401-
.vendor-tag:hover {
404+
.vendor-tag:hover, .vendor-tag.active {
402405
background-color: #d1d3e0;
403406
}
404407
.hover-panel {
405408
position: absolute;
406-
top: 20px;
409+
top: 100%;
407410
left: 0;
411+
margin-top: 4px;
408412
background: var(--input-bg);
409413
border: solid var(--border-width) var(--input-border);
410414
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
@@ -432,6 +436,28 @@ export default {
432436
text-decoration: underline;
433437
}
434438
439+
.panel-header {
440+
display: flex;
441+
justify-content: space-between;
442+
align-items: flex-start;
443+
margin-bottom: 8px;
444+
445+
h4 {
446+
margin: 0;
447+
font-size: 14px;
448+
}
449+
450+
.icon-close {
451+
cursor: pointer;
452+
font-size: 14px;
453+
color: var(--disabled-text);
454+
455+
&:hover {
456+
color: var(--text-color);
457+
}
458+
}
459+
}
460+
435461
.ref-item {
436462
margin-bottom: 10px;
437463
}

pkg/sbomscanner/components/__tests__/CveDetails.spec.ts

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -254,39 +254,54 @@ describe('CveDetails.vue', () => {
254254
});
255255
});
256256

257-
describe('User Interaction', () => {
258-
it('should show and hide vendor references on hover', async() => {
257+
describe('User Interaction (Advisory Links)', () => {
258+
it('should show references on vendor tag click and hide on subsequent click or close icon click', async() => {
259259
await flushPromises();
260260

261261
expect(wrapper.find('.hover-panel').exists()).toBe(false);
262262

263-
const wrapperEl = wrapper.find('.vendor-tags-wrapper');
263+
const vendorTags = wrapper.findAll('.vendor-tag');
264+
const firstVendorTag = vendorTags[0];
265+
const secondVendorTag = vendorTags[1];
266+
const firstVendorData = { name: 'Suse', references: ['https://suse.com/security/cve/CVE-2023-1234'] };
264267

265-
await wrapperEl.trigger('mouseenter');
266-
expect(wrapper.vm.inside).toBe(true);
267-
268-
const vendorTag = wrapper.find('.vendor-tag');
269-
270-
await vendorTag.trigger('mouseenter');
271-
expect(wrapper.vm.hoverVendor).toEqual({
272-
name: 'Suse',
273-
references: ['https://suse.com/security/cve/CVE-2023-1234']
274-
});
268+
await firstVendorTag.trigger('click');
269+
expect(wrapper.vm.selectedVendor).toEqual(firstVendorData);
275270

276271
await wrapper.vm.$nextTick();
277272

278-
const hoverPanel = wrapper.find('.hover-panel');
273+
let hoverPanel = wrapper.find('.hover-panel');
279274

280275
expect(hoverPanel.exists()).toBe(true);
281276
expect(hoverPanel.find('h4').text()).toBe(`References for ${ mockCveId }`);
282277
expect(hoverPanel.find('.ref-url').attributes('href')).toBe('https://suse.com/security/cve/CVE-2023-1234');
283278

284-
await wrapperEl.trigger('mouseleave');
285-
expect(wrapper.vm.inside).toBe(false);
286-
expect(wrapper.vm.hoverVendor).toBeNull();
279+
const secondVendorData = { name: 'Redhat', references: ['https://www.redhat.com/en/blog/press-release'] };
280+
281+
await secondVendorTag.trigger('click');
282+
expect(wrapper.vm.selectedVendor).toEqual(secondVendorData);
283+
284+
await wrapper.vm.$nextTick();
285+
hoverPanel = wrapper.find('.hover-panel');
286+
expect(hoverPanel.exists()).toBe(true);
287+
expect(hoverPanel.find('.ref-url').attributes('href')).toBe('https://www.redhat.com/en/blog/press-release');
288+
289+
const closeIcon = wrapper.find('.icon-close');
290+
291+
await closeIcon.trigger('click');
292+
expect(wrapper.vm.selectedVendor).toBeNull();
293+
294+
await wrapper.vm.$nextTick();
295+
expect(wrapper.find('.hover-panel').exists()).toBe(false);
296+
297+
await firstVendorTag.trigger('click');
298+
expect(wrapper.vm.selectedVendor).toEqual(firstVendorData);
299+
300+
await firstVendorTag.trigger('click');
301+
expect(wrapper.vm.selectedVendor).toBeNull();
287302

288303
await wrapper.vm.$nextTick();
289304
expect(wrapper.find('.hover-panel').exists()).toBe(false);
290305
});
291306
});
292-
});
307+
});

0 commit comments

Comments
 (0)