From b363af8021311dcaa78c9a19a4d04d517ff4e347 Mon Sep 17 00:00:00 2001 From: Gepeto Escalante Date: Thu, 12 Feb 2026 12:16: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 | 17 ++++++++ .../src/modules/components/domain/index.ts | 2 +- frontend/wailsjs/go/models.ts | 15 +++++-- 10 files changed, 121 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 2a24221a..597c6bb7 100644 --- a/app.go +++ b/app.go @@ -171,6 +171,18 @@ func (a *App) BuildMenu(keyboardService service.KeyboardService) *menu.Menu { runtime.EventsEmit(a.ctx, string(entities.ActionReplaceComponent)) }) + // 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)) + }) + // Skip submenu SkipMenu := ActionsMenu.AddSubmenu("Skip") SkipMenu.AddText("Skip file", nil, func(cd *menu.CallbackData) { diff --git a/backend/entities/component.go b/backend/entities/component.go index cbf1c25b..f587d9c7 100644 --- a/backend/entities/component.go +++ b/backend/entities/component.go @@ -89,13 +89,14 @@ const ( Include FilterAction = "include" Remove FilterAction = "remove" Replace FilterAction = "replace" + Ignore FilterAction = "ignore" ) 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=remove|eq=replace|eq=ignore"` 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..0d73dcaf 100644 --- a/backend/entities/keyboard.go +++ b/backend/entities/keyboard.go @@ -50,6 +50,9 @@ const ( ActionReplaceFile Action = "replaceFile" ActionReplaceFolder Action = "replaceFolder" ActionReplaceComponent Action = "replaceComponent" + ActionIgnoreFile Action = "ignoreFile" + ActionIgnoreComponent Action = "ignoreComponent" + ActionIgnoreFolder Action = "ignoreFolder" // Skip action (Scan settings) - always opens modal ActionSkipFile Action = "skipFile" @@ -106,6 +109,9 @@ var AllShortcutActions = []struct { {ActionReplaceFile, "ReplaceFile"}, {ActionReplaceFolder, "ReplaceFolder"}, {ActionReplaceComponent, "ReplaceComponent"}, + {ActionIgnoreFile, "IgnoreFile"}, + {ActionIgnoreComponent, "IgnoreComponent"}, + {ActionIgnoreFolder, "IgnoreFolder"}, {ActionSkipFile, "SkipFile"}, {ActionSkipFolder, "SkipFolder"}, {ActionSkipExtension, "SkipExtension"}, diff --git a/backend/entities/scanoss_settings.go b/backend/entities/scanoss_settings.go index f60bf5f9..5957eec9 100644 --- a/backend/entities/scanoss_settings.go +++ b/backend/entities/scanoss_settings.go @@ -74,6 +74,7 @@ type Bom struct { Include []ComponentFilter `json:"include,omitempty"` Remove []ComponentFilter `json:"remove,omitempty"` Replace []ComponentFilter `json:"replace,omitempty"` + Ignore []ComponentFilter `json:"ignore,omitempty"` } type ComponentFilter struct { @@ -89,6 +90,7 @@ type InitialFilters struct { Include []ComponentFilter Remove []ComponentFilter Replace []ComponentFilter + Ignore []ComponentFilter } // Priority returns the priority score for a ComponentFilter. @@ -197,8 +199,9 @@ func (sf *SettingsFile) GetResultWorkflowState(result Result) WorkflowState { included, _ := sf.IsResultIncluded(result) removed, _ := sf.IsResultRemoved(result) replaced, _ := sf.IsResultReplaced(result) + ignored, _ := sf.IsResultIgnored(result) - if included || removed || replaced { + if included || removed || replaced || ignored { return Completed } @@ -217,6 +220,10 @@ func (sf *SettingsFile) IsResultReplaced(result Result) (bool, int) { return sf.IsResultInList(result, sf.Bom.Replace) } +func (sf *SettingsFile) IsResultIgnored(result Result) (bool, int) { + return sf.IsResultInList(result, sf.Bom.Ignore) +} + func (sf *SettingsFile) IsResultInList(result Result, list []ComponentFilter) (bool, int) { matchedIndex := -1 var matchedFilter *ComponentFilter @@ -248,6 +255,9 @@ func (sf *SettingsFile) GetResultFilterConfig(result Result) FilterConfig { } else if replaced, i := sf.IsResultReplaced(result); replaced { filterAction = Replace filterType = getResultFilterType(sf.Bom.Replace[i]) + } else if ignored, i := sf.IsResultIgnored(result); ignored { + filterAction = Ignore + filterType = getResultFilterType(sf.Bom.Ignore[i]) } return FilterConfig{ @@ -282,6 +292,10 @@ func (sf *SettingsFile) GetBomEntryFromResult(result Result) ComponentFilter { return sf.Bom.Replace[i] } + if ignored, i := sf.IsResultIgnored(result); ignored { + return sf.Bom.Ignore[i] + } + return ComponentFilter{} } diff --git a/backend/repository/scanoss_settings_repository_json_impl.go b/backend/repository/scanoss_settings_repository_json_impl.go index 33f5cd52..e6068828 100644 --- a/backend/repository/scanoss_settings_repository_json_impl.go +++ b/backend/repository/scanoss_settings_repository_json_impl.go @@ -157,6 +157,8 @@ func (r *ScanossSettingsJsonRepository) AddBomEntry(newEntry entities.ComponentF targetList = &sf.Bom.Include case "replace": targetList = &sf.Bom.Replace + case "ignore": + targetList = &sf.Bom.Ignore default: return fmt.Errorf("invalid filter action: %s", filterAction) } @@ -174,6 +176,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.Replace = removeDuplicatesFromList(sf.Bom.Replace, newEntry) + sf.Bom.Ignore = removeDuplicatesFromList(sf.Bom.Ignore, newEntry) } func removeDuplicatesFromList(list []entities.ComponentFilter, newEntry entities.ComponentFilter) []entities.ComponentFilter { @@ -198,6 +201,7 @@ func (r *ScanossSettingsJsonRepository) ClearAllFilters() error { sf.Bom.Include = []entities.ComponentFilter{} sf.Bom.Remove = []entities.ComponentFilter{} sf.Bom.Replace = []entities.ComponentFilter{} + sf.Bom.Ignore = []entities.ComponentFilter{} return nil } @@ -207,13 +211,15 @@ func (r *ScanossSettingsJsonRepository) GetDeclaredPurls() []string { includedComponents := extractPurlsFromBom(sf.Bom.Include) removedComponents := extractPurlsFromBom(sf.Bom.Remove) replacedComponents := extractPurlsFromBom(sf.Bom.Replace) + ignoredComponents := extractPurlsFromBom(sf.Bom.Ignore) - totalLength := len(includedComponents) + len(removedComponents) + len(replacedComponents) + totalLength := len(includedComponents) + len(removedComponents) + len(replacedComponents) + len(ignoredComponents) declaredPurls := make([]string, 0, totalLength) declaredPurls = append(declaredPurls, includedComponents...) declaredPurls = append(declaredPurls, removedComponents...) declaredPurls = append(declaredPurls, replacedComponents...) + declaredPurls = append(declaredPurls, ignoredComponents...) return declaredPurls } diff --git a/backend/service/component_service_impl.go b/backend/service/component_service_impl.go index 2beadf24..73188c7e 100644 --- a/backend/service/component_service_impl.go +++ b/backend/service/component_service_impl.go @@ -91,6 +91,14 @@ func (s *ComponentServiceImpl) setInitialFilters() { Action: entities.Replace, }) } + 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, + }) + } } func (s *ComponentServiceImpl) ClearAllFilters() error { @@ -99,12 +107,13 @@ 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, remove, replace, ignore := sf.Bom.Include, sf.Bom.Remove, sf.Bom.Replace, sf.Bom.Ignore return entities.InitialFilters{ Include: include, Remove: remove, Replace: replace, + Ignore: ignore, } } diff --git a/frontend/src/components/FilterComponentActions.tsx b/frontend/src/components/FilterComponentActions.tsx index ccc4fcab..ae4c5cb7 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'; @@ -132,6 +132,11 @@ export default function FilterComponentActions() { replaceFolder: createModalActionHandler(FilterAction.Replace, 'folder'), replaceComponent: createModalActionHandler(FilterAction.Replace, 'component'), + // Ignore: file applies directly, others open modal + ignoreFile: createDirectActionHandler(FilterAction.Ignore), + ignoreFolder: createModalActionHandler(FilterAction.Ignore, 'folder'), + ignoreComponent: createModalActionHandler(FilterAction.Ignore, 'component'), + // Skip: always opens modal skipFile: createModalSkipHandler('file'), skipFolder: createModalSkipHandler('folder'), @@ -159,6 +164,11 @@ export default function FilterComponentActions() { useKeyboardShortcut(KEYBOARD_SHORTCUTS.replaceFolder.keys, handlers.replaceFolder, { enabled: filterEnabled }); useKeyboardShortcut(KEYBOARD_SHORTCUTS.replaceComponent.keys, handlers.replaceComponent, { 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 }); + // Skip useKeyboardShortcut(KEYBOARD_SHORTCUTS.skipFile.keys, handlers.skipFile, { enabled: skipEnabled }); useKeyboardShortcut(KEYBOARD_SHORTCUTS.skipFolder.keys, handlers.skipFolder, { enabled: skipEnabled }); @@ -178,6 +188,10 @@ export default function FilterComponentActions() { [entities.Action.ReplaceFile]: handlers.replaceFile, [entities.Action.ReplaceFolder]: handlers.replaceFolder, [entities.Action.ReplaceComponent]: handlers.replaceComponent, + // Ignore + [entities.Action.IgnoreFile]: handlers.ignoreFile, + [entities.Action.IgnoreComponent]: handlers.ignoreComponent, + [entities.Action.IgnoreFolder]: handlers.ignoreFolder, // Skip [entities.Action.SkipFile]: handlers.skipFile, [entities.Action.SkipFolder]: handlers.skipFolder, @@ -264,6 +278,31 @@ export default function FilterComponentActions() { + {/* Ignore */} + + + Ignore + + + + + File + G + + + Folder + Alt+Shift+G + + + Component + Shift+G + + + + {/* Separator */} diff --git a/frontend/src/lib/shortcuts.ts b/frontend/src/lib/shortcuts.ts index 262413d1..4b9ccd24 100644 --- a/frontend/src/lib/shortcuts.ts +++ b/frontend/src/lib/shortcuts.ts @@ -121,6 +121,23 @@ export const KEYBOARD_SHORTCUTS: Record = { keys: 'shift+r', }, + // Ignore + [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', + }, + [entities.Action.IgnoreFolder]: { + name: 'Ignore folder', + description: 'Open ignore dialog with folder selected', + keys: 'alt+shift+g', + }, + // Skip (Scan settings) - always opens modal [entities.Action.SkipFile]: { name: 'Skip file', 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..a7018532 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -18,6 +18,9 @@ export namespace entities { ReplaceFile = "replaceFile", ReplaceFolder = "replaceFolder", ReplaceComponent = "replaceComponent", + IgnoreFile = "ignoreFile", + IgnoreComponent = "ignoreComponent", + IgnoreFolder = "ignoreFolder", SkipFile = "skipFile", SkipFolder = "skipFolder", SkipExtension = "skipExtension", @@ -52,16 +55,18 @@ export namespace entities { include?: ComponentFilter[]; remove?: ComponentFilter[]; replace?: ComponentFilter[]; - + ignore?: 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.remove = this.convertValues(source["remove"], ComponentFilter); this.replace = this.convertValues(source["replace"], ComponentFilter); + this.ignore = this.convertValues(source["ignore"], ComponentFilter); } convertValues(a: any, classs: any, asMap: boolean = false): any { @@ -411,16 +416,18 @@ export namespace entities { Include: ComponentFilter[]; Remove: ComponentFilter[]; Replace: ComponentFilter[]; - + Ignore: 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.Remove = this.convertValues(source["Remove"], ComponentFilter); this.Replace = this.convertValues(source["Replace"], ComponentFilter); + this.Ignore = this.convertValues(source["Ignore"], ComponentFilter); } convertValues(a: any, classs: any, asMap: boolean = false): any {