Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [CI-11708] - Extract multiple Issues #29

Merged
merged 12 commits into from
Nov 17, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ drone-jira
NOTES*
output.json
.vscode/
.idea/
66 changes: 47 additions & 19 deletions plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/http"
"net/http/httputil"
"strconv"
"strings"
"time"

"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -73,6 +74,8 @@ type Args struct {
ConnectHostname string `envconfig:"PLUGIN_CONNECT_HOSTNAME"`
// Issue Keys(optional)
IssueKeys []string `envconfig:"PLUGIN_ISSUEKEYS"`

AdditionalMessage string `envconfig:"PLUGIN_ADDITIONAL_MESSAGE"`
}

// Exec executes the plugin.
Expand All @@ -81,7 +84,7 @@ func Exec(ctx context.Context, args Args) error {
environ = toEnvironment(args)
environmentID = toEnvironmentId(args)
environmentType = toEnvironmentType(args)
issues []string
issues = extractIssues(args)
HarnessAryan marked this conversation as resolved.
Show resolved Hide resolved
state = toState(args)
version = toVersion(args)
deeplink = toLink(args)
Expand All @@ -102,17 +105,24 @@ func Exec(ctx context.Context, args Args) error {
WithField("environment ID", environmentID)

//check if PLUGIN_ISSUEKEYS is provided
if len(args.IssueKeys) > 0 {
issues = args.IssueKeys
} else {
// fallback to extracting from commit if no issue keys are passed
var issue string = extractIssue(args)
if issue == "" {
logger.Debugln("cannot find issue number")
return errors.New("failed to extract issue number")
}
issues = []string{issue} // add the single issue here for consistency
}
// if len(args.IssueKeys) > 0 {
// issues = args.IssueKeys
// } else {
// // fallback to extracting from commit if no issue keys are passed
// var issue string = extractIssues(args)
// if issue == "" {
// logger.Debugln("cannot find issue number")
// return errors.New("failed to extract issue number")
// }
// issues = []string{issue} // add the single issue here for consistency
// }
if len(issues) == 0 {
logger.Debugln("cannot find issues")
return errors.New("failed to extract issues")
}
issues = removeDuplicates(issues)
HarnessAryan marked this conversation as resolved.
Show resolved Hide resolved
logger = logger.WithField("issues", strings.Join(issues, ","))
logger.Debugln("successfully extracted all issues")

commitMessage := args.Commit.Message
if len(commitMessage) > 255 {
Expand Down Expand Up @@ -195,7 +205,7 @@ func Exec(ctx context.Context, args Args) error {
// validation of arguments
if (args.ClientID == "" && args.ClientSecret == "") && (args.ConnnectKey == "") {
logger.Debugln("client id and secret are empty. specify the client id and secret or specify connect key")
return errors.New("No client id & secret or connect token & hostname provided")
return errors.New("no client id & secret or connect token & hostname provided")
}
// create tokens and deployments
if args.ClientID != "" && args.ClientSecret != "" {
Expand Down Expand Up @@ -302,7 +312,7 @@ func getOauthToken(args Args) (string, error) {
return "", err
}
if res.StatusCode > 299 {
return "", fmt.Errorf("Error code %d", res.StatusCode)
return "", fmt.Errorf("error code %d", res.StatusCode)
}
output := map[string]interface{}{}
err = json.Unmarshal(out, &output)
Expand Down Expand Up @@ -356,7 +366,7 @@ func createDeployment(payload DeploymentPayload, cloudID, debug, oauthToken stri
logrus.WithField("status", res.Status).WithField("response", outString).Info("request complete")
}
if res.StatusCode > 299 {
return fmt.Errorf("Error code %d", res.StatusCode)
return fmt.Errorf("error code %d", res.StatusCode)
}
return nil
}
Expand Down Expand Up @@ -387,7 +397,7 @@ func createConnectDeployment(payload DeploymentPayload, cloudID, debug, jwtToken
logrus.WithField("status", res.Status).WithField("response", outString).Info("request complete")
}
if res.StatusCode > 299 {
return fmt.Errorf("Error code %d", res.StatusCode)
return fmt.Errorf("error code %d", res.StatusCode)
}
return nil
}
Expand Down Expand Up @@ -418,7 +428,7 @@ func createConnectBuild(payload BuildPayload, cloudID, debug, jwtToken string) e
logrus.WithField("status", res.Status).WithField("response", outString).Info("request complete")
}
if res.StatusCode > 299 {
return fmt.Errorf("Error code %d", res.StatusCode)
return fmt.Errorf("error code %d", res.StatusCode)
}
return nil
}
Expand All @@ -428,7 +438,7 @@ func getCloudID(instance, cloudID string) (string, error) {

tenant, err := lookupTenant(instance)
if err != nil {
return "", fmt.Errorf("Cannot get cloudid from instance, %s", err)
return "", fmt.Errorf("cannot get cloudid from instance, %s", err)
}
return tenant.ID, nil
}
Expand All @@ -447,9 +457,27 @@ func lookupTenant(tenant string) (*Tenant, error) {
}
defer res.Body.Close()
if res.StatusCode > 299 {
return nil, fmt.Errorf("Error code %d", res.StatusCode)
return nil, fmt.Errorf("error code %d", res.StatusCode)
}
out := new(Tenant)
err = json.NewDecoder(res.Body).Decode(out)
return out, err
}

func removeDuplicates(list []string) []string {
// Create an empty map to store seen elements
seen := make(map[string]bool)

// Initialize a new list to store unique elements
uniqueList := []string{}

for _, element := range list {
// Check if the element is already seen
if !seen[element] {
seen[element] = true
uniqueList = append(uniqueList, element)
}
}

return uniqueList
}
HarnessAryan marked this conversation as resolved.
Show resolved Hide resolved
25 changes: 14 additions & 11 deletions plugin/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ import (
// helper function to extract the issue number from
// the commit details, including the commit message,
// branch and pull request title.
func extractIssue(args Args) string {
return regexp.MustCompile(args.Project + "\\-\\d+").FindString(
fmt.Sprintln(
args.Commit.Message,
args.PullRequest.Title,
args.Commit.Source,
args.Commit.Target,
args.Commit.Branch,
),
)
func extractIssues(args Args) []string {

regex := regexp.MustCompile(args.Project+"-\\d+")
HarnessAryan marked this conversation as resolved.
Show resolved Hide resolved
matches := regex.FindAllString(fmt.Sprintln(
args.AdditionalMessage,
args.Commit.Message,
args.PullRequest.Title,
args.Commit.Source,
args.Commit.Target,
args.Commit.Branch,
), -1)

HarnessAryan marked this conversation as resolved.
Show resolved Hide resolved
return matches
HarnessAryan marked this conversation as resolved.
Show resolved Hide resolved
}

// helper function determines the pipeline state.
Expand Down Expand Up @@ -152,4 +155,4 @@ func toStateEnum(s string) string {
default:
return "unknown"
}
}
}
125 changes: 86 additions & 39 deletions plugin/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,91 @@ package plugin

import "testing"

func TestExtractIssue(t *testing.T) {
tests := []struct {
text string
want string
}{
{
text: "TEST-1 this is a test",
want: "TEST-1",
},
{
text: "suffix [TEST-123]",
want: "TEST-123",
},
{
text: "[TEST-123] prefix",
want: "TEST-123",
},
{
text: "TEST-123 prefix",
want: "TEST-123",
},
{
text: "feature/TEST-123",
want: "TEST-123",
},
{
text: "no issue",
want: "",
},
}
for _, test := range tests {
var args Args
args.Commit.Message = test.text
args.Project = "TEST"
if got, want := extractIssue(args), test.want; got != want {
t.Errorf("Got issue number %v, want %v", got, want)
}
}
// compareSlices checks if s2 is a subset of s1
func compareSlices(s1, s2 []string) bool {
// Special case: if both slices are empty, they're equal
if len(s1) == 0 && len(s2) == 0 {
return true
}

// If s2 is empty but s1 isn't, or s1 is shorter than s2, they can't match
if len(s2) == 0 || len(s1) < len(s2) {
return false
}

// For each possible starting position in s1
for i := 0; i <= len(s1)-len(s2); i++ {
allMatch := true
// Try to match all elements of s2 starting at position i
for j := 0; j < len(s2); j++ {
if s1[i+j] != s2[j] {
allMatch = false
break
}
}
if allMatch {
return true
}
}
return false
}

func TestExtractIssues(t *testing.T) {
tests := []struct {
name string
text string
want []string
}{
{
name: "Single issue",
text: "TEST-1 this is a test",
want: []string{"TEST-1"},
},
{
name: "Two issues in brackets",
text: "suffix [TEST-123] [TEST-234]",
want: []string{"TEST-123", "TEST-234"},
},
{
name: "Two issues, one in prefix",
text: "[TEST-123] prefix [TEST-456]",
want: []string{"TEST-123"},
},
{
name: "Multiple comma-separated issues",
text: "Multiple issues: TEST-123, TEST-234, TEST-456",
want: []string{"TEST-123", "TEST-234", "TEST-456"},
},
{
name: "Mixed format issues",
text: "feature/TEST-123 [TEST-456] and [TEST-789]",
want: []string{"TEST-123", "TEST-456", "TEST-789"},
},
{
name: "Space-separated issues",
text: "TEST-123 TEST-456 TEST-789",
want: []string{"TEST-123", "TEST-456", "TEST-789"},
},
{
name: "No issues",
text: "no issue",
want: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var args Args
args.Commit.Message = tt.text
args.Project = "TEST"

got := extractIssues(args)

if !compareSlices(got, tt.want) {
t.Errorf("\ngot: %v\nwant: %v", got, tt.want)
}
})
}
}

func TestExtractInstanceName(t *testing.T) {
Expand Down Expand Up @@ -130,4 +177,4 @@ func TestToEnvironmentType(t *testing.T) {
}
})
}
}
}