From 8d945526b4e2cf1c044d9df98b557de927873a9c Mon Sep 17 00:00:00 2001 From: Gepeto Escalante Date: Tue, 17 Feb 2026 01:30:32 +0000 Subject: [PATCH] [SP-3624] Add 'ignore' menu option to scanoss-cc Implemented by Gepeto Escalante. JIRA: https://scanoss.atlassian.net/browse/SP-3624 --- app.go | 12 ++++++ backend/entities/component.go | 3 +- backend/entities/keyboard.go | 6 +++ backend/entities/scanoss_settings.go | 16 +++++++- .../scanoss_settings_repository_json_impl.go | 8 +++- backend/service/component_service_impl.go | 11 ++++- .../src/components/FilterComponentActions.tsx | 41 ++++++++++++++++++- frontend/src/lib/shortcuts.ts | 15 +++++++ .../src/modules/components/domain/index.ts | 2 +- frontend/wailsjs/go/models.ts | 15 +++++-- 10 files changed, 119 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 2a24221a..8880ef59 100644 --- a/app.go +++ b/app.go @@ -147,6 +147,18 @@ func (a *App) BuildMenu(keyboardService service.KeyboardService) *menu.Menu { runtime.EventsEmit(a.ctx, string(entities.ActionIncludeComponent)) }) + // Ignore submenu + IgnoreMenu := ActionsMenu.AddSubmenu("Ignore") + IgnoreMenu.AddText("Ignore file", nil, func(cd *menu.CallbackData) { + runtime.EventsEmit(a.ctx, string(entities.ActionIgnoreFile)) + }) + IgnoreMenu.AddText("Ignore folder", nil, func(cd *menu.CallbackData) { + runtime.EventsEmit(a.ctx, string(entities.ActionIgnoreFolder)) + }) + IgnoreMenu.AddText("Ignore component", nil, func(cd *menu.CallbackData) { + runtime.EventsEmit(a.ctx, string(entities.ActionIgnoreComponent)) + }) + // Dismiss submenu DismissMenu := ActionsMenu.AddSubmenu("Dismiss") DismissMenu.AddText("Dismiss file", nil, func(cd *menu.CallbackData) { diff --git a/backend/entities/component.go b/backend/entities/component.go index cbf1c25b..04540616 100644 --- a/backend/entities/component.go +++ b/backend/entities/component.go @@ -87,6 +87,7 @@ type FilterAction string const ( Include FilterAction = "include" + Ignore FilterAction = "ignore" Remove FilterAction = "remove" Replace FilterAction = "replace" ) @@ -95,7 +96,7 @@ type ComponentFilterDTO struct { Path string `json:"path,omitempty"` Purl string `json:"purl,omitempty"` Usage string `json:"usage,omitempty"` - Action FilterAction `json:"action" validate:"required,eq=include|eq=remove|eq=replace"` + Action FilterAction `json:"action" validate:"required,eq=include|eq=ignore|eq=remove|eq=replace"` Comment string `json:"comment,omitempty"` ReplaceWith string `json:"replace_with,omitempty" validate:"omitempty,valid-purl"` License string `json:"license,omitempty"` diff --git a/backend/entities/keyboard.go b/backend/entities/keyboard.go index 13fa713a..00850ec3 100644 --- a/backend/entities/keyboard.go +++ b/backend/entities/keyboard.go @@ -44,6 +44,9 @@ const ( ActionIncludeFile Action = "includeFile" ActionIncludeComponent Action = "includeComponent" ActionIncludeFolder Action = "includeFolder" + ActionIgnoreFile Action = "ignoreFile" + ActionIgnoreComponent Action = "ignoreComponent" + ActionIgnoreFolder Action = "ignoreFolder" ActionDismissFile Action = "dismissFile" ActionDismissComponent Action = "dismissComponent" ActionDismissFolder Action = "dismissFolder" @@ -100,6 +103,9 @@ var AllShortcutActions = []struct { {ActionIncludeFile, "IncludeFile"}, {ActionIncludeComponent, "IncludeComponent"}, {ActionIncludeFolder, "IncludeFolder"}, + {ActionIgnoreFile, "IgnoreFile"}, + {ActionIgnoreComponent, "IgnoreComponent"}, + {ActionIgnoreFolder, "IgnoreFolder"}, {ActionDismissFile, "DismissFile"}, {ActionDismissComponent, "DismissComponent"}, {ActionDismissFolder, "DismissFolder"}, diff --git a/backend/entities/scanoss_settings.go b/backend/entities/scanoss_settings.go index f60bf5f9..f5abf203 100644 --- a/backend/entities/scanoss_settings.go +++ b/backend/entities/scanoss_settings.go @@ -72,6 +72,7 @@ type SizesSkipSettings struct { type Bom struct { Include []ComponentFilter `json:"include,omitempty"` + Ignore []ComponentFilter `json:"ignore,omitempty"` Remove []ComponentFilter `json:"remove,omitempty"` Replace []ComponentFilter `json:"replace,omitempty"` } @@ -87,6 +88,7 @@ type ComponentFilter struct { type InitialFilters struct { Include []ComponentFilter + Ignore []ComponentFilter Remove []ComponentFilter Replace []ComponentFilter } @@ -195,10 +197,11 @@ func (sf *SettingsFile) Equal(other *SettingsFile) (bool, error) { func (sf *SettingsFile) GetResultWorkflowState(result Result) WorkflowState { included, _ := sf.IsResultIncluded(result) + ignored, _ := sf.IsResultIgnored(result) removed, _ := sf.IsResultRemoved(result) replaced, _ := sf.IsResultReplaced(result) - if included || removed || replaced { + if included || ignored || removed || replaced { return Completed } @@ -209,6 +212,10 @@ func (sf *SettingsFile) IsResultIncluded(result Result) (bool, int) { return sf.IsResultInList(result, sf.Bom.Include) } +func (sf *SettingsFile) IsResultIgnored(result Result) (bool, int) { + return sf.IsResultInList(result, sf.Bom.Ignore) +} + func (sf *SettingsFile) IsResultRemoved(result Result) (bool, int) { return sf.IsResultInList(result, sf.Bom.Remove) } @@ -242,6 +249,9 @@ func (sf *SettingsFile) GetResultFilterConfig(result Result) FilterConfig { if included, i := sf.IsResultIncluded(result); included { filterAction = Include filterType = getResultFilterType(sf.Bom.Include[i]) + } else if ignored, i := sf.IsResultIgnored(result); ignored { + filterAction = Ignore + filterType = getResultFilterType(sf.Bom.Ignore[i]) } else if removed, i := sf.IsResultRemoved(result); removed { filterAction = Remove filterType = getResultFilterType(sf.Bom.Remove[i]) @@ -274,6 +284,10 @@ func (sf *SettingsFile) GetBomEntryFromResult(result Result) ComponentFilter { return sf.Bom.Include[i] } + if ignored, i := sf.IsResultIgnored(result); ignored { + return sf.Bom.Ignore[i] + } + if removed, i := sf.IsResultRemoved(result); removed { return sf.Bom.Remove[i] } diff --git a/backend/repository/scanoss_settings_repository_json_impl.go b/backend/repository/scanoss_settings_repository_json_impl.go index 33f5cd52..4c732a6d 100644 --- a/backend/repository/scanoss_settings_repository_json_impl.go +++ b/backend/repository/scanoss_settings_repository_json_impl.go @@ -155,6 +155,8 @@ func (r *ScanossSettingsJsonRepository) AddBomEntry(newEntry entities.ComponentF targetList = &sf.Bom.Remove case "include": targetList = &sf.Bom.Include + case "ignore": + targetList = &sf.Bom.Ignore case "replace": targetList = &sf.Bom.Replace default: @@ -173,6 +175,7 @@ func (r *ScanossSettingsJsonRepository) removeDuplicatesFromAllLists(newEntry en sf.Bom.Remove = removeDuplicatesFromList(sf.Bom.Remove, newEntry) sf.Bom.Include = removeDuplicatesFromList(sf.Bom.Include, newEntry) + sf.Bom.Ignore = removeDuplicatesFromList(sf.Bom.Ignore, newEntry) sf.Bom.Replace = removeDuplicatesFromList(sf.Bom.Replace, newEntry) } @@ -196,6 +199,7 @@ func isDuplicate(entry, newEntry entities.ComponentFilter) bool { func (r *ScanossSettingsJsonRepository) ClearAllFilters() error { sf := r.GetSettings() sf.Bom.Include = []entities.ComponentFilter{} + sf.Bom.Ignore = []entities.ComponentFilter{} sf.Bom.Remove = []entities.ComponentFilter{} sf.Bom.Replace = []entities.ComponentFilter{} return nil @@ -205,13 +209,15 @@ func (r *ScanossSettingsJsonRepository) GetDeclaredPurls() []string { sf := r.GetSettings() includedComponents := extractPurlsFromBom(sf.Bom.Include) + ignoredComponents := extractPurlsFromBom(sf.Bom.Ignore) removedComponents := extractPurlsFromBom(sf.Bom.Remove) replacedComponents := extractPurlsFromBom(sf.Bom.Replace) - totalLength := len(includedComponents) + len(removedComponents) + len(replacedComponents) + totalLength := len(includedComponents) + len(ignoredComponents) + len(removedComponents) + len(replacedComponents) declaredPurls := make([]string, 0, totalLength) declaredPurls = append(declaredPurls, includedComponents...) + declaredPurls = append(declaredPurls, ignoredComponents...) declaredPurls = append(declaredPurls, removedComponents...) declaredPurls = append(declaredPurls, replacedComponents...) diff --git a/backend/service/component_service_impl.go b/backend/service/component_service_impl.go index 2beadf24..00c65afe 100644 --- a/backend/service/component_service_impl.go +++ b/backend/service/component_service_impl.go @@ -75,6 +75,14 @@ func (s *ComponentServiceImpl) setInitialFilters() { Action: entities.Include, }) } + for _, ignore := range initialFilters.Ignore { + s.initialFilters = append(s.initialFilters, entities.ComponentFilterDTO{ + Path: ignore.Path, + Purl: ignore.Purl, + Usage: string(ignore.Usage), + Action: entities.Ignore, + }) + } for _, remove := range initialFilters.Remove { s.initialFilters = append(s.initialFilters, entities.ComponentFilterDTO{ Path: remove.Path, @@ -99,10 +107,11 @@ func (s *ComponentServiceImpl) ClearAllFilters() error { func (s *ComponentServiceImpl) GetInitialFilters() entities.InitialFilters { sf := s.scanossSettingsRepo.GetSettings() - include, remove, replace := sf.Bom.Include, sf.Bom.Remove, sf.Bom.Replace + include, ignore, remove, replace := sf.Bom.Include, sf.Bom.Ignore, sf.Bom.Remove, sf.Bom.Replace return entities.InitialFilters{ Include: include, + Ignore: ignore, Remove: remove, Replace: replace, } diff --git a/frontend/src/components/FilterComponentActions.tsx b/frontend/src/components/FilterComponentActions.tsx index ccc4fcab..c3750f89 100644 --- a/frontend/src/components/FilterComponentActions.tsx +++ b/frontend/src/components/FilterComponentActions.tsx @@ -21,7 +21,7 @@ * SOFTWARE. */ -import { Check, EyeOff, PackageMinus, Replace } from 'lucide-react'; +import { Ban, Check, EyeOff, PackageMinus, Replace } from 'lucide-react'; import { useCallback, useMemo, useState } from 'react'; import useKeyboardShortcut from '@/hooks/useKeyboardShortcut'; @@ -122,6 +122,11 @@ export default function FilterComponentActions() { includeFolder: createModalActionHandler(FilterAction.Include, 'folder'), includeComponent: createModalActionHandler(FilterAction.Include, 'component'), + // Ignore: file applies directly, others open modal + ignoreFile: createDirectActionHandler(FilterAction.Ignore), + ignoreFolder: createModalActionHandler(FilterAction.Ignore, 'folder'), + ignoreComponent: createModalActionHandler(FilterAction.Ignore, 'component'), + // Dismiss: file applies directly, others open modal dismissFile: createDirectActionHandler(FilterAction.Remove), dismissFolder: createModalActionHandler(FilterAction.Remove, 'folder'), @@ -149,6 +154,11 @@ export default function FilterComponentActions() { useKeyboardShortcut(KEYBOARD_SHORTCUTS.includeComponent.keys, handlers.includeComponent, { enabled: filterEnabled }); useKeyboardShortcut(KEYBOARD_SHORTCUTS.includeFolder.keys, handlers.includeFolder, { enabled: filterEnabled }); + // Ignore + useKeyboardShortcut(KEYBOARD_SHORTCUTS.ignoreFile.keys, handlers.ignoreFile, { enabled: filterEnabled }); + useKeyboardShortcut(KEYBOARD_SHORTCUTS.ignoreComponent.keys, handlers.ignoreComponent, { enabled: filterEnabled }); + useKeyboardShortcut(KEYBOARD_SHORTCUTS.ignoreFolder.keys, handlers.ignoreFolder, { enabled: filterEnabled }); + // Dismiss useKeyboardShortcut(KEYBOARD_SHORTCUTS.dismissFile.keys, handlers.dismissFile, { enabled: filterEnabled }); useKeyboardShortcut(KEYBOARD_SHORTCUTS.dismissComponent.keys, handlers.dismissComponent, { enabled: filterEnabled }); @@ -170,6 +180,10 @@ export default function FilterComponentActions() { [entities.Action.IncludeFile]: handlers.includeFile, [entities.Action.IncludeComponent]: handlers.includeComponent, [entities.Action.IncludeFolder]: handlers.includeFolder, + // Ignore + [entities.Action.IgnoreFile]: handlers.ignoreFile, + [entities.Action.IgnoreComponent]: handlers.ignoreComponent, + [entities.Action.IgnoreFolder]: handlers.ignoreFolder, // Dismiss [entities.Action.DismissFile]: handlers.dismissFile, [entities.Action.DismissComponent]: handlers.dismissComponent, @@ -214,6 +228,31 @@ export default function FilterComponentActions() { + {/* Ignore */} + + + Ignore + + + + + File + G + + + Folder + Alt+Shift+G + + + Component + Shift+G + + + + {/* Dismiss */} = { description: 'Open include dialog with folder selected', keys: 'alt+shift+i', }, + [entities.Action.IgnoreFile]: { + name: 'Ignore file', + description: 'Ignore file directly', + keys: 'g, f5', + }, + [entities.Action.IgnoreComponent]: { + name: 'Ignore component', + description: 'Open ignore dialog with component selected', + keys: 'shift+g, shift+f5', + }, + [entities.Action.IgnoreFolder]: { + name: 'Ignore folder', + description: 'Open ignore dialog with folder selected', + keys: 'alt+shift+g', + }, [entities.Action.DismissFile]: { name: 'Dismiss file', description: 'Dismiss file directly', diff --git a/frontend/src/modules/components/domain/index.ts b/frontend/src/modules/components/domain/index.ts index 09c92a0f..e9465b79 100644 --- a/frontend/src/modules/components/domain/index.ts +++ b/frontend/src/modules/components/domain/index.ts @@ -31,7 +31,7 @@ export enum FilterAction { export type FilterBy = 'path' | 'purl'; export const filterActionLabelMap: Record = { - [FilterAction.Ignore]: 'Omit / Skip', + [FilterAction.Ignore]: 'Ignore', [FilterAction.Include]: 'Include', [FilterAction.Remove]: 'Dismiss', [FilterAction.Replace]: 'Replace', diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index a8d12ef2..5c280971 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -12,6 +12,9 @@ export namespace entities { IncludeFile = "includeFile", IncludeComponent = "includeComponent", IncludeFolder = "includeFolder", + IgnoreFile = "ignoreFile", + IgnoreComponent = "ignoreComponent", + IgnoreFolder = "ignoreFolder", DismissFile = "dismissFile", DismissComponent = "dismissComponent", DismissFolder = "dismissFolder", @@ -50,16 +53,18 @@ export namespace entities { } export class Bom { include?: ComponentFilter[]; + ignore?: ComponentFilter[]; remove?: ComponentFilter[]; replace?: ComponentFilter[]; - + static createFrom(source: any = {}) { return new Bom(source); } - + constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.include = this.convertValues(source["include"], ComponentFilter); + this.ignore = this.convertValues(source["ignore"], ComponentFilter); this.remove = this.convertValues(source["remove"], ComponentFilter); this.replace = this.convertValues(source["replace"], ComponentFilter); } @@ -409,16 +414,18 @@ export namespace entities { } export class InitialFilters { Include: ComponentFilter[]; + Ignore: ComponentFilter[]; Remove: ComponentFilter[]; Replace: ComponentFilter[]; - + static createFrom(source: any = {}) { return new InitialFilters(source); } - + constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.Include = this.convertValues(source["Include"], ComponentFilter); + this.Ignore = this.convertValues(source["Ignore"], ComponentFilter); this.Remove = this.convertValues(source["Remove"], ComponentFilter); this.Replace = this.convertValues(source["Replace"], ComponentFilter); }