Skip to content

Commit

Permalink
feat: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ShawkyZ committed Jan 22, 2025
1 parent 9e5f127 commit 96b1235
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 48 deletions.
40 changes: 21 additions & 19 deletions domain/ide/workspace/folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,14 +474,20 @@ func appendTestResults(sic snyk.SeverityIssueCounts, results []json_schemas.Test
}

func (f *Folder) FilterAndPublishDiagnostics(p product.Product) {
productIssuesByFile, err := f.GetDelta(p)
if err != nil {
// Error can only be returned from delta analysis. Other non delta scans are skipped with no errors.
err = fmt.Errorf("couldn't determine the difference between current and base branch for %s scan. %w", p.ToProductNamesString(), err)
var filteringErr error
issueByFile := f.IssuesByProduct()[p]
if f.c.IsDeltaFindingsEnabled() {
deltaIssues, err := f.GetDelta(p)
issueByFile = deltaIssues
if err != nil {
// Error can only be returned from delta analysis. Other non delta scans are skipped with no errors.
filteringErr = fmt.Errorf("couldn't determine the difference between current and base branch for %s scan. %w", p.ToProductNamesString(), err)
}
}

// Trigger publishDiagnostics for all issues in Cache.
// Filtered issues will be sent with an empty slice if no issues exist.
filteredIssues := f.filterDiagnostics(productIssuesByFile[p])
filteredIssues := f.filterDiagnostics(issueByFile)
filteredIssuesToSend := snyk.IssuesByFile{}

for path := range f.IssuesByProduct()[p] {
Expand All @@ -491,19 +497,16 @@ func (f *Folder) FilterAndPublishDiagnostics(p product.Product) {
for path, issues := range filteredIssues {
filteredIssuesToSend[path] = issues
}
f.publishDiagnostics(p, filteredIssuesToSend, err)
f.publishDiagnostics(p, filteredIssuesToSend, filteringErr)
}

func (f *Folder) GetDelta(p product.Product) (snyk.ProductIssuesByFile, error) {
func (f *Folder) GetDelta(p product.Product) (snyk.IssuesByFile, error) {
logger := f.c.Logger().With().Str("method", "getDelta").Logger()
productIssueByFile := f.IssuesByProduct()
if !f.c.IsDeltaFindingsEnabled() {
return productIssueByFile, nil
}
issueByFile := f.IssuesByProduct()[p]

if len(productIssueByFile[p]) == 0 {
if len(issueByFile) == 0 {
// If no issues found in current branch scan. We can't have deltas.
return productIssueByFile, nil
return issueByFile, nil
}

baseIssueList, err := f.scanPersister.GetPersistedIssueList(f.path, p)
Expand All @@ -512,7 +515,7 @@ func (f *Folder) GetDelta(p product.Product) (snyk.ProductIssuesByFile, error) {
return nil, delta.ErrNoDeltaCalculated
}

currentFlatIssueList := getFlatIssueList(productIssueByFile, p)
currentFlatIssueList := getFlatIssueList(issueByFile)
baseFindingIdentifiable := make([]delta.Identifiable, len(baseIssueList))
for i := range baseIssueList {
baseFindingIdentifiable[i] = &baseIssueList[i]
Expand All @@ -527,7 +530,7 @@ func (f *Folder) GetDelta(p product.Product) (snyk.ProductIssuesByFile, error) {

if err != nil {
logger.Error().Err(err).Msg("couldn't calculate delta")
return productIssueByFile, delta.ErrNoDeltaCalculated
return issueByFile, delta.ErrNoDeltaCalculated
}

deltaSnykIssues := make([]snyk.Issue, len(diff))
Expand All @@ -538,13 +541,12 @@ func (f *Folder) GetDelta(p product.Product) (snyk.ProductIssuesByFile, error) {
}
deltaSnykIssues[i] = *issue
}
productIssueByFile[p] = getIssuePerFileFromFlatList(deltaSnykIssues)
issueByFile = getIssuePerFileFromFlatList(deltaSnykIssues)

return productIssueByFile, nil
return issueByFile, nil
}

func getFlatIssueList(productIssueByFile snyk.ProductIssuesByFile, p product.Product) []snyk.Issue {
issueByFile := productIssueByFile[p]
func getFlatIssueList(issueByFile snyk.IssuesByFile) []snyk.Issue {
var currentFlatIssueList []snyk.Issue
for _, issueList := range issueByFile {
currentFlatIssueList = append(currentFlatIssueList, issueList...)
Expand Down
43 changes: 39 additions & 4 deletions domain/scanstates/scan_state_aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,20 @@ type StateSnapshot struct {
AllScansSucceededWorkingDirectory bool
AnyScanErrorReference bool
AnyScanErrorWorkingDirectory bool
TotalScansCount int
ScansInProgressCount int
ScansErrorCount int
ScansSuccessCount int
}

func (agg *ScanStateAggregator) StateSnapshot() StateSnapshot {
agg.mu.RLock()
defer agg.mu.RUnlock()

return agg.stateSnapshot()
}

func (agg *ScanStateAggregator) stateSnapshot() StateSnapshot {
ss := StateSnapshot{
AllScansStartedReference: agg.allScansStarted(true),
AllScansStartedWorkingDirectory: agg.allScansStarted(false),
Expand All @@ -83,6 +91,10 @@ func (agg *ScanStateAggregator) StateSnapshot() StateSnapshot {
AllScansSucceededWorkingDirectory: agg.allScansSucceeded(false),
AnyScanErrorReference: agg.anyScanError(true),
AnyScanErrorWorkingDirectory: agg.anyScanError(false),
TotalScansCount: agg.totalScansCount(),
ScansInProgressCount: agg.scansCountInState(InProgress),
ScansSuccessCount: agg.scansCountInState(Success),
ScansErrorCount: agg.scansCountInState(Error),
}
return ss
}
Expand All @@ -103,16 +115,17 @@ func NewScanStateAggregator(c *config.Config, ssce ScanStateChangeEmitter) Aggre

func (agg *ScanStateAggregator) Init(folders []string) {
agg.mu.Lock()
agg.mu.Unlock()
defer agg.mu.Unlock()

for _, f := range folders {
agg.initForAllProducts(f)
}
// Emit after init to send first summary
agg.scanStateChangeEmitter.Emit(agg.StateSnapshot())
agg.scanStateChangeEmitter.Emit(agg.stateSnapshot())
}

func (agg *ScanStateAggregator) initForAllProducts(folderPath string) {
// TODO: Add or remove from the map if a product is on/off
agg.referenceScanStates[FolderProductKey{Product: product.ProductOpenSource, FolderPath: folderPath}] = &ScanState{Status: NotStarted}
agg.referenceScanStates[FolderProductKey{Product: product.ProductCode, FolderPath: folderPath}] = &ScanState{Status: NotStarted}
agg.referenceScanStates[FolderProductKey{Product: product.ProductInfrastructureAsCode, FolderPath: folderPath}] = &ScanState{Status: NotStarted}
Expand All @@ -129,7 +142,7 @@ func (agg *ScanStateAggregator) AddNewFolder(folderPath string) {

agg.initForAllProducts(folderPath)

agg.scanStateChangeEmitter.Emit(agg.StateSnapshot())
agg.scanStateChangeEmitter.Emit(agg.stateSnapshot())
}

// SetScanState changes the Status field of the existing state (or creates it if it doesn't exist).
Expand Down Expand Up @@ -160,7 +173,7 @@ func (agg *ScanStateAggregator) setScanState(folderPath string, p product.Produc
st.Status = newState.Status
st.Err = newState.Err

agg.scanStateChangeEmitter.Emit(agg.StateSnapshot())
agg.scanStateChangeEmitter.Emit(agg.stateSnapshot())
}

func (agg *ScanStateAggregator) SetScanDone(folderPath string, p product.Product, isReferenceScan bool, scanErr error) {
Expand Down Expand Up @@ -268,3 +281,25 @@ func (agg *ScanStateAggregator) anyScanError(isReference bool) bool {
}
return false
}

func (agg *ScanStateAggregator) totalScansCount() int {
scansCount := len(agg.referenceScanStates) + len(agg.workingDirectoryScanStates)
return scansCount
}

func (agg *ScanStateAggregator) scansCountInState(status ScanStatus) int {
count := 0

for _, st := range agg.workingDirectoryScanStates {
if st.Status == status {
count++
}
}
for _, st := range agg.referenceScanStates {
if st.Status == status {
count++
}
}

return count
}
6 changes: 4 additions & 2 deletions domain/scanstates/summary_emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func NewSummaryEmitter(c *config.Config, n notification.Notifier) *Emitter {
}

func (s *Emitter) Emit(state StateSnapshot) {
generatedHtml := s.renderer.GetSummaryHtml(state)
go s.notifier.Send(types.ScanSummary{ScanSummary: generatedHtml})
go func() {
generatedHtml := s.renderer.GetSummaryHtml(state)
s.notifier.Send(types.ScanSummary{ScanSummary: generatedHtml})
}()
}
84 changes: 72 additions & 12 deletions domain/scanstates/summary_html.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import (
_ "embed"
"html/template"

"github.com/snyk/snyk-ls/domain/snyk"

"github.com/snyk/snyk-ls/application/config"
"github.com/snyk/snyk-ls/domain/snyk"
"github.com/snyk/snyk-ls/domain/snyk/delta"
"github.com/snyk/snyk-ls/internal/product"
)

//go:embed template/details.html
Expand Down Expand Up @@ -53,11 +54,29 @@ func NewHtmlRenderer(c *config.Config) (*HtmlRenderer, error) {

func (renderer *HtmlRenderer) GetSummaryHtml(state StateSnapshot) string {
logger := renderer.c.Logger().With().Str("method", "GetSummaryHtml").Logger()
issueCount := renderer.getIssuesFromFolders()
var allIssues []snyk.Issue
var deltaIssues []snyk.Issue
var currentIssuesFound int
var currentFixableIssueCount int
if state.AnyScanSucceededReference || state.AnyScanSucceededWorkingDirectory {
allIssues, deltaIssues = renderer.getIssuesFromFolders()
isDeltaEnabled := renderer.c.IsDeltaFindingsEnabled()

if isDeltaEnabled {
currentIssuesFound = len(deltaIssues)
currentFixableIssueCount = fixableIssueCount(deltaIssues)
} else {
currentIssuesFound = len(allIssues)
currentFixableIssueCount = fixableIssueCount(allIssues)
}
}

data := map[string]interface{}{
"Styles": template.CSS(summaryStylesTemplate),
"IssuesFound": issueCount,
"FixableIssueCount": 7,
"IssuesFound": len(allIssues),
"NewIssuesFound": len(deltaIssues),
"CurrentIssuesFound": currentIssuesFound,
"CurrentFixableIssueCount": currentFixableIssueCount,
"AllScansStartedReference": state.AllScansStartedReference,
"AllScansStartedWorkingDirectory": state.AllScansStartedWorkingDirectory,
"AnyScanInProgressReference": state.AnyScanInProgressReference,
Expand All @@ -68,6 +87,8 @@ func (renderer *HtmlRenderer) GetSummaryHtml(state StateSnapshot) string {
"AllScansSucceededWorkingDirectory": state.AllScansSucceededWorkingDirectory,
"AnyScanErrorReference": state.AnyScanErrorReference,
"AnyScanErrorWorkingDirectory": state.AnyScanErrorWorkingDirectory,
"TotalScansCount": state.TotalScansCount,
"RunningScansCount": state.ScansSuccessCount + state.ScansErrorCount,
}
var buffer bytes.Buffer
if err := renderer.globalTemplate.Execute(&buffer, data); err != nil {
Expand All @@ -78,17 +99,56 @@ func (renderer *HtmlRenderer) GetSummaryHtml(state StateSnapshot) string {
return buffer.String()
}

func (renderer *HtmlRenderer) getIssuesFromFolders() int {
var allIssues []snyk.Issue
func (renderer *HtmlRenderer) getIssuesFromFolders() (allIssues []snyk.Issue, deltaIssues []snyk.Issue) {
logger := renderer.c.Logger().With().Str("method", "getIssuesFromFolders").Logger()

for _, f := range renderer.c.Workspace().Folders() {
if dp, ok := f.(delta.Provider); ok {
deltaIssues = append(deltaIssues, renderer.getDeltaIssuesForFolder(dp)...)
} else {
logger.Error().Msgf("Failed to get cast folder %s to interface delta.Provider", f.Name())
}

ip, ok := renderer.c.Workspace().(snyk.IssueProvider)
if !ok {
return 0
ip, ok := f.(snyk.IssueProvider)
if !ok {
logger.Error().Msgf("Failed to get cast folder %s to interface snyk.IssueProvider", f.Name())
return allIssues, deltaIssues
}
for _, issues := range ip.Issues() {
allIssues = append(allIssues, issues...)
}
}

for _, issues := range ip.Issues() {
return allIssues, deltaIssues
}

func (renderer *HtmlRenderer) getDeltaIssuesForFolder(dp delta.Provider) []snyk.Issue {
var allIssues []snyk.Issue

allIssues = append(allIssues, getDeltaForProduct(product.ProductOpenSource, dp)...)
allIssues = append(allIssues, getDeltaForProduct(product.ProductCode, dp)...)
allIssues = append(allIssues, getDeltaForProduct(product.ProductInfrastructureAsCode, dp)...)

return allIssues
}

func getDeltaForProduct(p product.Product, dp delta.Provider) []snyk.Issue {
var allIssues []snyk.Issue
issuesByFile, err := dp.GetDelta(p)
if err != nil {
return allIssues
}
for _, issues := range issuesByFile {
allIssues = append(allIssues, issues...)
}
return len(allIssues)
return allIssues
}

func fixableIssueCount(issues []snyk.Issue) (fixableIssueCount int) {
for _, issue := range issues {
if issue.AdditionalData.IsFixable() {
fixableIssueCount++
}
}
return fixableIssueCount
}
9 changes: 6 additions & 3 deletions domain/scanstates/template/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<h2 class="snx-title snx-h2">Snyk is scanning for issues...</h2>
<p class="snx-message is-dimmed">
<span class="snx-loader size-s"></span>
<strong class="snx-highlight"> 4/8 </strong> base &amp; reference scans
<strong class="snx-highlight"> {{.RunningScansCount}}/{{.TotalScansCount}} </strong> base &amp; reference scans
</p>
</div>
{{end}}
Expand All @@ -38,15 +38,18 @@ <h2 class="snx-title snx-h2">Snyk is scanning for issues...</h2>
<div class="snx-actions">
<div class="snx-toggle">
{{.IssuesFound}} total issues
{{if .AnyScanSucceededReference}}
{{.NewIssuesFound}} new issues
{{end}}
</div>
</div>
{{end}}
</div>

<div class="snx-summary">
{{if or .AnyScanSucceededReference .AnyScanSucceededWorkingDirectory }}
<p class="snx-message">⚠️ <span class="snx-highlight">{{.IssuesFound}} issues</span> found in your project</p>
<p class="snx-message"><span class="snx-highlight">{{.FixableIssueCount}} issues</span> are fixable.</p>
<p class="snx-message">⚠️ <span class="snx-highlight">{{.CurrentIssuesFound}} issues</span> found in your project</p>
<p class="snx-message"><span class="snx-highlight">{{.CurrentFixableIssueCount}} issues</span> are fixable.</p>
{{else}}
<p class="snx-message">⚠️ Scanning for issues in your project.</p>
<p class="snx-message">⚡ Identifying fixable issues.</p>
Expand Down
2 changes: 1 addition & 1 deletion domain/snyk/delta/delta_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ import (
)

type Provider interface {
GetDelta(p product.Product) (snyk.ProductIssuesByFile, error)
GetDelta(p product.Product) (snyk.IssuesByFile, error)
}
17 changes: 10 additions & 7 deletions domain/snyk/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,20 +305,23 @@ func (sc *DelegatingConcurrentScanner) Scan(
processResults(data, true, true)
go func() {
defer referenceBranchScanWaitGroup.Done()
// TODO: implement proper context handling
sc.scanStateAggregator.SetScanInProgress(folderPath, scanner.Product(), true)
err := sc.scanBaseBranch(context.Background(), s, folderPath, gitCheckoutHandler)
sc.scanStateAggregator.SetScanDone(folderPath, scanner.Product(), true, err)
isFullScan := path == folderPath
if isFullScan {
// TODO: implement proper context handling
sc.scanStateAggregator.SetScanInProgress(folderPath, scanner.Product(), true)
err := sc.scanBaseBranch(context.Background(), s, folderPath, gitCheckoutHandler)
sc.scanStateAggregator.SetScanDone(folderPath, scanner.Product(), true, err)
if err != nil {
logger.Error().Err(err).Msgf("couldn't scan base branch for folder %s for product %s", folderPath, s.Product())
}
}
// TODO: is this a good idea?
data = snyk.ScanData{
Product: s.Product(),
Path: gitCheckoutHandler.BaseFolderPath(),
}
processResults(data, false, false)
// TODO: where should we report errors ?
if err != nil {
logger.Error().Err(err).Msgf("couldn't scan base branch for folder %s for product %s", folderPath, s.Product())
}
}()

logger.Info().Msgf("Scanning %s with %T: COMPLETE found %v issues", path, s, len(foundIssues))
Expand Down

0 comments on commit 96b1235

Please sign in to comment.