Skip to content

Instantly share code, notes, and snippets.

@automaticgiant
Last active February 19, 2025 14:04
Show Gist options
  • Select an option

  • Save automaticgiant/25042b0d6478391f3e4dcd87d2cdd0a6 to your computer and use it in GitHub Desktop.

Select an option

Save automaticgiant/25042b0d6478391f3e4dcd87d2cdd0a6 to your computer and use it in GitHub Desktop.
age the TechRefined jira label after 30 days
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