Skip to content

Commit cabeb0e

Browse files
rushk014xingzhang-suse
authored andcommitted
fix(images): Fix Image name sorting bug in grouped view (#323)
Refactor image name construction into `constructImageName` util. Generate sortable field for grouped table to use
1 parent d532e86 commit cabeb0e

File tree

8 files changed

+74
-22
lines changed

8 files changed

+74
-22
lines changed

pkg/sbomscanner-ui-ext/components/ImageDetails.vue

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ import VulnerabilityTable from './common/VulnerabilityTable';
172172
import DownloadSBOMBtn from './common/DownloadSBOMBtn';
173173
import DownloadFullReportBtn from './common/DownloadFullReportBtn.vue';
174174
import { getHighestScore, getSeverityNum, getScoreNum } from '../utils/report';
175+
import { constructImageName } from '@pkg/utils/image';
175176
176177
export default {
177178
name: 'ImageDetails',
@@ -254,14 +255,7 @@ export default {
254255
displayImageName() {
255256
if (!this.currentImage) return this.imageName;
256257
257-
const metadata = this.currentImage.imageMetadata;
258-
259-
if (metadata?.registryURI && metadata?.repository && metadata?.tag) {
260-
return `${ metadata.registryURI }/${ metadata.repository }:${ metadata.tag }`;
261-
}
262-
263-
// Fallback to the hash ID if metadata is not available
264-
return this.imageName;
258+
return constructImageName(this.currentImage.imageMetadata) || this.imageName;
265259
},
266260
267261
// Get the vulnerability report for this image

pkg/sbomscanner-ui-ext/config/table-headers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export const REPO_BASED_IMAGE_LIST_TABLE = [
232232
name: 'image',
233233
labelKey: 'imageScanner.images.listTable.headers.imageName',
234234
formatter: 'ImageNameCell',
235-
sort: 'name',
235+
sort: 'imageReference',
236236
width: 300,
237237
},
238238
{

pkg/sbomscanner-ui-ext/formatters/ImageNameCell.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
PRODUCT_NAME,
99
PAGE,
1010
} from '@pkg/types';
11+
import { constructImageName } from '@pkg/utils/image';
1112
export default {
1213
props: {
1314
row: {
@@ -23,7 +24,7 @@ export default {
2324
},
2425
computed: {
2526
displayName() {
26-
return `${ this.row.imageMetadata?.registryURI }/${ this.row.imageMetadata?.repository }:${ this.row.imageMetadata?.tag }`;
27+
return this.row.imageReference ? this.row.imageReference : constructImageName(this.row.imageMetadata);
2728
}
2829
}
2930
};

pkg/sbomscanner-ui-ext/formatters/__tests__/ImageNameCell.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('ImageNameCell.vue', () => {
4343
it('should handle missing imageMetadata gracefully', () => {
4444
const mockRow = { metadata: { name: 'my-image-resource-name-2' } };
4545

46-
const expectedDisplayName = 'undefined/undefined:undefined';
46+
const expectedDisplayName = '';
4747
const expectedUrl = `/c/${ mockClusterId }/mocked-product/mocked-images-page/my-image-resource-name-2`;
4848

4949
const wrapper = mountComponent(mockRow);

pkg/sbomscanner-ui-ext/models/storage.sbomscanner.kubewarden.io.vulnerabilityreport.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SteveModel from '@shell/plugins/steve/steve-class';
22
import { PRODUCT_NAME, PAGE } from '@pkg/types';
3+
import { constructImageName } from '@pkg/utils/image';
34

45
export default class VulnerabilityReport extends SteveModel {
56
get _availableActions() {
@@ -133,6 +134,6 @@ export default class VulnerabilityReport extends SteveModel {
133134
}
134135

135136
get imageReference() {
136-
return `${ this.imageMetadata.registryURI }/${ this.imageMetadata.repository }:${ this.imageMetadata.tag }`;
137+
return constructImageName(this.imageMetadata);
137138
}
138139
}

pkg/sbomscanner-ui-ext/pages/c/_cluster/sbomscanner/ImageOverview.vue

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ import { Checkbox } from '@components/Form/Checkbox';
186186
import { RESOURCE } from '@pkg/types';
187187
import { imageDetailsToCSV } from '@pkg/utils/report';
188188
import { saveAs } from 'file-saver';
189+
import { constructImageName } from '@pkg/utils/image';
189190
import Papa from 'papaparse';
190191
import _ from 'lodash';
191192
import day from 'dayjs';
@@ -294,7 +295,7 @@ export default {
294295
filteredRows() {
295296
const filters = this.debouncedFilters;
296297
const filteredRows = this.rows.filter((row) => {
297-
const imageName = `${ row.imageMetadata.registryURI }/${ row.imageMetadata.repository }:${ row.imageMetadata.tag }`;
298+
const imageName = constructImageName(row.imageMetadata);
298299
const imageMatch = !filters.imageSearch || imageName.toLowerCase().includes(filters.imageSearch.toLowerCase());
299300
const severityMatch = filters.severitySearch === 'any' || row.report.summary[filters.severitySearch] > 0;
300301
const repositoryMatch = filters.repositorySearch === 'Any' || row.imageMetadata.repository === filters.repositorySearch;
@@ -376,7 +377,7 @@ export default {
376377
377378
const imageList = imagesData.map((row) => {
378379
return {
379-
'IMAGE REFERENCE': isDataGrouped ? `${ row.imageMetadata.registryURI }/${ row.imageMetadata.repository }:${ row.imageMetadata.tag }` : row.imageReference,
380+
'IMAGE REFERENCE': isDataGrouped ? constructImageName(row.imageMetadata) : row.imageReference,
380381
'CVEs(Critical)': isDataGrouped ? row.scanResult.critical : row.report.summary.critical,
381382
'CVEs(High)': isDataGrouped ? row.scanResult.high : row.report.summary.high,
382383
'CVEs(Medium)': isDataGrouped ? row.scanResult.medium : row.report.summary.medium,
@@ -517,10 +518,11 @@ export default {
517518
}
518519
currRepo.images.push(
519520
{
520-
id: report.id,
521-
imageMetadata: report.imageMetadata,
522-
metadata: { name: report.metadata.name },
523-
scanResult: currImageScanResult,
521+
id: report.id,
522+
imageMetadata: report.imageMetadata,
523+
metadata: { name: report.metadata.name },
524+
imageReference: constructImageName(report.imageMetadata),
525+
scanResult: currImageScanResult,
524526
}
525527
);
526528
repoMap.set(mapKey, currRepo);
@@ -533,10 +535,11 @@ export default {
533535
cveCntByRepo: { ...currImageScanResult },
534536
images: [
535537
{
536-
id: report.id,
537-
imageMetadata: report.imageMetadata,
538-
metadata: { name: report.metadata.name },
539-
scanResult: currImageScanResult,
538+
id: report.id,
539+
imageMetadata: report.imageMetadata,
540+
metadata: { name: report.metadata.name },
541+
imageReference: constructImageName(report.imageMetadata),
542+
scanResult: currImageScanResult,
540543
}
541544
]
542545
};

pkg/sbomscanner-ui-ext/pages/c/_cluster/sbomscanner/__tests__/ImageOverview.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,45 @@ describe('ImageOverview.vue', () => {
168168
expect(repo.cveCntByRepo.high).toBe(3);
169169
});
170170

171+
test('preprocessData adds imageReference for sorting in grouped view', () => {
172+
const wrapper = mountComponent();
173+
174+
const vulReports = [
175+
{
176+
id: '2',
177+
imageMetadata: {
178+
repository: 'test-repo', registry: 'my-reg', registryURI: 'my-reg', tag: 'zulu'
179+
},
180+
metadata: { name: 'img-z', namespace: 'ns' },
181+
report: { summary: { critical: 1 } }
182+
},
183+
{
184+
id: '1',
185+
imageMetadata: {
186+
repository: 'test-repo', registry: 'my-reg', registryURI: 'my-reg', tag: 'alpha'
187+
},
188+
metadata: { name: 'img-a', namespace: 'ns' },
189+
report: { summary: { critical: 1 } }
190+
}
191+
];
192+
193+
const processedData = (wrapper.vm as any).preprocessData(vulReports);
194+
195+
// Expect one group for 'test-repo'
196+
expect(processedData).toHaveLength(1);
197+
const repoGroup = processedData[0];
198+
199+
// Check that imageReference is correctly constructed
200+
expect(repoGroup.images[0].imageReference).toBe('my-reg/test-repo:zulu');
201+
expect(repoGroup.images[1].imageReference).toBe('my-reg/test-repo:alpha');
202+
203+
// Simulate SortableTable's sorting on the 'imageReference' field
204+
repoGroup.images.sort((a: any, b: any) => a.imageReference.localeCompare(b.imageReference));
205+
206+
// Verify the sorted order
207+
expect(repoGroup.images[0].imageReference).toBe('my-reg/test-repo:alpha');
208+
});
209+
171210
test('downloadCSVReport handles grouped rows and saveAs failure', async() => {
172211
const wrapper = mountComponent();
173212

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
interface ImageMetadata {
2+
registryURI?: string;
3+
repository?: string;
4+
tag?: string;
5+
[key: string]: any;
6+
}
7+
8+
export function constructImageName(imageMetadata: ImageMetadata): string {
9+
if (imageMetadata?.registryURI && imageMetadata?.repository && imageMetadata?.tag) {
10+
return `${ imageMetadata.registryURI }/${ imageMetadata.repository }:${ imageMetadata.tag }`;
11+
}
12+
13+
return '';
14+
}

0 commit comments

Comments
 (0)