From 1958e000b62a655766961d6cafb0242a62008d4b Mon Sep 17 00:00:00 2001 From: Cata Date: Thu, 11 Jul 2024 14:16:08 +0100 Subject: [PATCH] refactor: format expiration date for the ignored details panel [IDE-489] (#581) --- infrastructure/code/code_html.go | 22 +++++++++++++- infrastructure/code/code_html_test.go | 30 ++++++++++++++++++- infrastructure/code/code_test.go | 2 +- infrastructure/code/convert.go | 15 ++++++---- infrastructure/code/convert_test.go | 8 ++--- .../code/fake_code_client_scanner.go | 2 +- 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/infrastructure/code/code_html.go b/infrastructure/code/code_html.go index ec5b29a66..8637a1ee5 100644 --- a/infrastructure/code/code_html.go +++ b/infrastructure/code/code_html.go @@ -138,7 +138,7 @@ func getLineToIgnoreAction(issue snyk.Issue) int { func prepareIgnoreDetailsRow(ignoreDetails *snyk.IgnoreDetails) []IgnoreDetail { return []IgnoreDetail{ {"Category", parseCategory(ignoreDetails.Category)}, - {"Expiration", ignoreDetails.Expiration}, + {"Expiration", formatExpirationDate(ignoreDetails.Expiration)}, {"Ignored On", formatDate(ignoreDetails.IgnoredOn)}, {"Ignored By", ignoreDetails.IgnoredBy}, {"Reason", ignoreDetails.Reason}, @@ -246,6 +246,26 @@ func getRepoName(commitURL string) string { return tabTitle } +func formatExpirationDate(expiration string) string { + if expiration == "" { + return "No expiration" + } + parsedDate, err := time.Parse(time.RFC3339, expiration) + if err != nil { + return expiration // Original string if parsing fails + } + + // Calculate the difference in days + daysRemaining := int(time.Until(parsedDate).Hours() / 24) + + if daysRemaining < 0 { + return "Expired" + } else if daysRemaining == 1 { + return "1 day" + } + return fmt.Sprintf("%d days", daysRemaining) +} + func formatDate(date time.Time) string { month := date.Format("January") return fmt.Sprintf("%s %d, %d", month, date.Day(), date.Year()) diff --git a/infrastructure/code/code_html_test.go b/infrastructure/code/code_html_test.go index 542a11bb6..fca793ee6 100644 --- a/infrastructure/code/code_html_test.go +++ b/infrastructure/code/code_html_test.go @@ -136,7 +136,7 @@ func Test_Code_Html_getCodeDetailsHtml_ignored(t *testing.T) { IgnoreDetails: &snyk.IgnoreDetails{ Category: "wont-fix", Reason: getIgnoreReason("long"), - Expiration: "13 days", + Expiration: "", IgnoredOn: time.Now(), IgnoredBy: "John Smith", }, @@ -163,11 +163,39 @@ func Test_Code_Html_getCodeDetailsHtml_ignored(t *testing.T) { assert.Contains(t, codePanelHtml, `class="ignore-badge"`) assert.Contains(t, codePanelHtml, `data-content="ignore-details"`) assert.Contains(t, codePanelHtml, `class="ignore-details-value">Ignored permanently`) + assert.Contains(t, codePanelHtml, `class="ignore-details-value">No expiration`) // Because category is "wont-fix" // assert Footer buttons are not present when issue is ignored assert.NotContains(t, codePanelHtml, `id="ignore-actions"`) } +func Test_Code_Html_getCodeDetailsHtml_ignored_expired(t *testing.T) { + _ = testutil.UnitTest(t) + + issue := snyk.Issue{ + ID: "scala/DontUsePrintStackTrace", + Severity: 2, + LessonUrl: "https://learn.snyk.io/lesson/no-rate-limiting/?loc=ide", + CWEs: []string{"CWE-123", "CWE-456"}, + IsIgnored: true, + IgnoreDetails: &snyk.IgnoreDetails{ + Category: "temporary-ignore", + Reason: getIgnoreReason("long"), + Expiration: "2023-08-26T13:16:53.177Z", + IgnoredOn: time.Now(), + IgnoredBy: "John Smith", + }, + AdditionalData: snyk.CodeIssueData{}, + } + + // invoke method under test + codePanelHtml := getCodeDetailsHtml(issue) + + // assert Ignore Details section + // Asserting an expired date to prevent the test from breaking in the future as the current date changes + assert.Contains(t, codePanelHtml, `class="ignore-details-value">Expired`) +} + func Test_Code_Html_getCodeDetailsHtml_ignored_customEndpoint(t *testing.T) { c := testutil.UnitTest(t) diff --git a/infrastructure/code/code_test.go b/infrastructure/code/code_test.go index 71f595f8f..4f40149a5 100644 --- a/infrastructure/code/code_test.go +++ b/infrastructure/code/code_test.go @@ -339,7 +339,7 @@ func TestUploadAndAnalyzeWithIgnores(t *testing.T) { assert.Equal(t, true, issues[1].IsIgnored) assert.Equal(t, "wont-fix", issues[1].IgnoreDetails.Category) assert.Equal(t, "False positive", issues[1].IgnoreDetails.Reason) - assert.Equal(t, "13 days", issues[1].IgnoreDetails.Expiration) + assert.Equal(t, "2024-07-11T10:06:44Z", issues[1].IgnoreDetails.Expiration) assert.Equal(t, 2024, issues[1].IgnoreDetails.IgnoredOn.Year()) assert.Equal(t, "Neil M", issues[1].IgnoreDetails.IgnoredBy) diff --git a/infrastructure/code/convert.go b/infrastructure/code/convert.go index ab4dd7cad..08a8240f6 100644 --- a/infrastructure/code/convert.go +++ b/infrastructure/code/convert.go @@ -410,10 +410,6 @@ func (s *SarifConverter) getIgnoreDetails(result codeClientSarif.Result) (bool, } isIgnored = true suppression := result.Suppressions[0] - expiration := "" - if suppression.Properties.Expiration != nil { - expiration = *suppression.Properties.Expiration - } reason := suppression.Justification if reason == "" { @@ -422,7 +418,7 @@ func (s *SarifConverter) getIgnoreDetails(result codeClientSarif.Result) (bool, ignoreDetails = &snyk.IgnoreDetails{ Category: string(suppression.Properties.Category), Reason: reason, - Expiration: expiration, + Expiration: parseExpirationDateFromString(suppression.Properties.Expiration), IgnoredOn: parseDateFromString(suppression.Properties.IgnoredOn), IgnoredBy: suppression.Properties.IgnoredBy.Name, } @@ -430,6 +426,15 @@ func (s *SarifConverter) getIgnoreDetails(result codeClientSarif.Result) (bool, return isIgnored, ignoreDetails } +func parseExpirationDateFromString(date *string) string { + if date == nil { + return "" + } + + parsedDate := parseDateFromString(*date) + return parsedDate.Format(time.RFC3339) +} + func parseDateFromString(date string) time.Time { logger := config.CurrentConfig().Logger().With().Str("method", "convert.parseDateFromString").Logger() layouts := []string{ diff --git a/infrastructure/code/convert_test.go b/infrastructure/code/convert_test.go index 9091bbc81..71d0cde58 100644 --- a/infrastructure/code/convert_test.go +++ b/infrastructure/code/convert_test.go @@ -928,7 +928,7 @@ func Test_Result_getIgnoreDetails(t *testing.T) { }) t.Run("does return ignore details if one suppression", func(t *testing.T) { - expiration := "expiration" + expiration := "2024-08-06T13:16:53Z" r := codeClientSarif.Result{ Message: codeClientSarif.ResultMessage{ Text: "", @@ -957,13 +957,13 @@ func Test_Result_getIgnoreDetails(t *testing.T) { assert.NotNil(t, ignoreDetails) assert.Equal(t, "reason", ignoreDetails.Reason) assert.Equal(t, "category", ignoreDetails.Category) - assert.Equal(t, "expiration", ignoreDetails.Expiration) + assert.Equal(t, expiration, ignoreDetails.Expiration) assert.Equal(t, 2024, ignoreDetails.IgnoredOn.Year()) assert.Equal(t, "name", ignoreDetails.IgnoredBy) }) t.Run("sets reason to a default value if justification not provided in suppression", func(t *testing.T) { - expiration := "expiration" + expiration := "2024-08-06T13:16:53Z" r := codeClientSarif.Result{ Message: codeClientSarif.ResultMessage{ Text: "", @@ -991,7 +991,7 @@ func Test_Result_getIgnoreDetails(t *testing.T) { assert.NotNil(t, ignoreDetails) assert.Equal(t, "None given", ignoreDetails.Reason) assert.Equal(t, "category", ignoreDetails.Category) - assert.Equal(t, "expiration", ignoreDetails.Expiration) + assert.Equal(t, expiration, ignoreDetails.Expiration) assert.Equal(t, 2024, ignoreDetails.IgnoredOn.Year()) assert.Equal(t, "name", ignoreDetails.IgnoredBy) }) diff --git a/infrastructure/code/fake_code_client_scanner.go b/infrastructure/code/fake_code_client_scanner.go index aade6fe19..133f81af2 100644 --- a/infrastructure/code/fake_code_client_scanner.go +++ b/infrastructure/code/fake_code_client_scanner.go @@ -291,7 +291,7 @@ func getSarifResponseJson2(filePath string) string { "justification": "False positive", "properties": { "category": "wont-fix", - "expiration": "13 days", + "expiration": "2024-07-11T10:06:44Z", "ignoredOn": "2024-02-23T16:08:25Z", "ignoredBy": { "name": "Neil M",