Last active
February 19, 2025 14:04
-
-
Save automaticgiant/25042b0d6478391f3e4dcd87d2cdd0a6 to your computer and use it in GitHub Desktop.
age the TechRefined jira label after 30 days
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "log" | |
| "os" | |
| "strconv" | |
| "time" | |
| jira "github.com/andygrunwald/go-jira" | |
| "github.com/joho/godotenv" | |
| ) | |
| func main() { | |
| // Load .env file if it exists | |
| godotenv.Load() | |
| // Get environment variables | |
| apiToken := os.Getenv("JIRA_API_TOKEN") | |
| userEmail := os.Getenv("JIRA_USER_EMAIL") | |
| baseURL := os.Getenv("JIRA_BASE_URL") | |
| projectKey := os.Getenv("PROJECT_KEY") | |
| // Validate required environment variables | |
| if apiToken == "" || userEmail == "" || baseURL == "" { | |
| log.Fatal("JIRA_API_TOKEN, JIRA_USER_EMAIL, and JIRA_BASE_URL must be set") | |
| } | |
| // Get dry run setting from environment (defaults to true for safety) | |
| dryRun := true | |
| if dryRunEnv := os.Getenv("DRY_RUN"); dryRunEnv != "" { | |
| var err error | |
| dryRun, err = strconv.ParseBool(dryRunEnv) | |
| if err != nil { | |
| log.Fatal("Invalid DRY_RUN value. Must be true or false") | |
| } | |
| } | |
| if dryRun { | |
| fmt.Println("Running in dry-run mode. No changes will be made.") | |
| } | |
| // Create Jira client | |
| tp := jira.BasicAuthTransport{ | |
| Username: userEmail, | |
| Password: apiToken, | |
| } | |
| client, err := jira.NewClient(tp.Client(), baseURL) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| var ( | |
| totalIssues = 0 | |
| matchingIssues = 0 | |
| toUpdateIssues = 0 | |
| ) | |
| // Get total backlog count first | |
| jqlAll := fmt.Sprintf(`project = "%s"`, projectKey) | |
| _, resp, err := client.Issue.Search(jqlAll, &jira.SearchOptions{MaxResults: 0}) | |
| if err != nil { | |
| log.Fatalf("Error getting total count: %v", err) | |
| } | |
| totalIssues = resp.Total // Fix: Use response Total instead of len() | |
| // Search for issues with TechRefined label | |
| jql := fmt.Sprintf(`labels = TechRefined AND project = "%s" ORDER BY updated DESC`, projectKey) | |
| searchOpts := &jira.SearchOptions{ | |
| StartAt: 0, | |
| MaxResults: 100, | |
| Expand: "changelog", | |
| Fields: []string{"labels", "summary", "updated", "changelog"}, // Only fetch fields we need | |
| } | |
| var allIssues []jira.Issue | |
| for { | |
| issues, resp, err := client.Issue.Search(jql, searchOpts) | |
| if err != nil { | |
| log.Fatalf("Error searching issues: %v", err) | |
| } | |
| allIssues = append(allIssues, issues...) | |
| if resp.StartAt+resp.MaxResults >= resp.Total { | |
| matchingIssues = resp.Total // Fix: Use response Total for matching count | |
| break | |
| } | |
| searchOpts.StartAt += resp.MaxResults | |
| } | |
| thresholdDate := time.Now().AddDate(0, 0, -30) // 30 days ago | |
| // Process issues | |
| for _, issue := range allIssues { | |
| lastLabelAdd, lastChangeDesc := getLastLabelChange(issue) | |
| if !lastLabelAdd.IsZero() && lastLabelAdd.Before(thresholdDate) { | |
| toUpdateIssues++ // Increment counter before any updates | |
| if dryRun { | |
| fmt.Printf("[DRY RUN] Would update %s (%s): TechRefined -> TechRefined-aged\n"+ | |
| "Last label change: %s - %s\n", | |
| issue.Key, | |
| issue.Fields.Summary, | |
| lastLabelAdd.Format("2006-01-02"), | |
| lastChangeDesc) | |
| continue | |
| } | |
| update := map[string]interface{}{ | |
| "update": map[string]interface{}{ | |
| "labels": []map[string]interface{}{ | |
| { | |
| "remove": "TechRefined", | |
| }, | |
| { | |
| "add": "TechRefined-aged", | |
| }, | |
| }, | |
| }, | |
| } | |
| _, err := client.Issue.UpdateIssue(issue.Key, update) | |
| if err != nil { | |
| log.Printf("Error updating %s: %v", issue.Key, err) | |
| } else { | |
| fmt.Printf("Updated %s (%s): TechRefined -> TechRefined-aged\n", issue.Key, issue.Fields.Summary) | |
| } | |
| } | |
| } | |
| fmt.Printf("\nSummary:\n") | |
| fmt.Printf("Total tickets in backlog: %d\n", totalIssues) | |
| fmt.Printf("Tickets with TechRefined label: %d\n", matchingIssues) | |
| if dryRun { | |
| fmt.Printf("Tickets that would be updated: %d\n", toUpdateIssues) | |
| } else { | |
| fmt.Printf("Tickets updated: %d\n", toUpdateIssues) | |
| } | |
| } | |
| func needsUpdate(issue jira.Issue, thresholdDate time.Time) bool { | |
| if issue.Changelog == nil { | |
| return false | |
| } | |
| lastLabelAdd := time.Time{} | |
| for _, history := range issue.Changelog.Histories { | |
| for _, item := range history.Items { | |
| if item.Field == "labels" && item.ToString == "TechRefined" { | |
| historyTime, err := time.Parse("2006-01-02T15:04:05.999-0700", history.Created) | |
| if err != nil { | |
| continue | |
| } | |
| if historyTime.After(lastLabelAdd) { | |
| lastLabelAdd = historyTime | |
| } | |
| } | |
| } | |
| } | |
| return !lastLabelAdd.IsZero() && lastLabelAdd.Before(thresholdDate) | |
| } | |
| // Add helper function to get last label change details | |
| func getLastLabelChange(issue jira.Issue) (time.Time, string) { | |
| var lastLabelAdd time.Time | |
| var changeDesc string | |
| if issue.Changelog == nil { | |
| return lastLabelAdd, changeDesc | |
| } | |
| for _, history := range issue.Changelog.Histories { | |
| for _, item := range history.Items { | |
| if item.Field == "labels" && item.ToString == "TechRefined" { | |
| historyTime, err := time.Parse("2006-01-02T15:04:05.999-0700", history.Created) | |
| if err != nil { | |
| continue | |
| } | |
| if historyTime.After(lastLabelAdd) { | |
| lastLabelAdd = historyTime | |
| changeDesc = fmt.Sprintf("From: %s, To: %s", item.FromString, item.ToString) | |
| } | |
| } | |
| } | |
| } | |
| return lastLabelAdd, changeDesc | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment