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 {