Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions internal/cli/subcommands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type SubcommandList struct {
debugFlag bool
flagset *flag.FlagSet
filter string
filterSet bool
helpFlag bool
parameters map[string]interface{}
windowWidth int
Expand All @@ -43,16 +42,10 @@ func (subcommand *SubcommandList) InitFlags(args []string) error {
subcommand.flagset.IntVar(&subcommand.windowWidth, "w", 0, "Window width")
subcommand.flagset.StringVar(&subcommand.filter, "filter", "", "The filter name to use for listing tickets with")
subcommand.flagset.StringVar(&subcommand.filter, "f", "", "The filter name to use for listing tickets with")
subcommand.flagset.BoolVar(&subcommand.filterSet, "set-filter", false, "Requires the filter name parameter. If true, save the name of the filter as the default filter to use for future list operations.")
if err := subcommand.flagset.Parse(args); err != nil {
return err
}

// If filterSet is true, then filter is required
if subcommand.filterSet && subcommand.filter == "" {
return fmt.Errorf("Filter name is required when using the --set-filter flag")
}

subcommand.parameters["debugFlag"] = debugFlag
subcommand.parameters["helpFlag"] = helpFlag
subcommand.parameters["windowWidth"] = window
Expand All @@ -61,7 +54,7 @@ func (subcommand *SubcommandList) InitFlags(args []string) error {

// Execute is used to list tickets when the user uses the list subcommand from the CLI
func (subcommand *SubcommandList) Execute() {
err := ticket.HandleList(os.Stdout, subcommand.windowWidth, common.BranchName, subcommand.filter, subcommand.filterSet, subcommand.debugFlag)
err := ticket.HandleList(os.Stdout, subcommand.windowWidth, common.BranchName, subcommand.filter, subcommand.debugFlag)
if err != nil {
fmt.Println(err)
return
Expand Down
1 change: 1 addition & 0 deletions pkg/common/ticketInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ package common
type TicketInterface interface {
TicketFilename() string
TicketToYaml() []byte
ToAny() (map[string]any, error)
}
141 changes: 92 additions & 49 deletions pkg/ticket/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@ import (

// Filter is used in ticket list operations to return a subset of tickets
type Filter struct {
Name string
Filter string

Name string
Filter string
CreatedAt string
}

// FilterList is a list of Filters and a value that represents the 'current'
// filter to use
type FilterList struct {
CurrentFilter string
Filters map[string]Filter
Filters map[string]Filter
}

// FilterTicketsByID takes a list of tickets and an integer representing the
Expand Down Expand Up @@ -306,47 +304,66 @@ func WriteFilters(filters *FilterList, commitMessage string, branchName string,
func checkFilterIsValid(filter string, name string, debugFlag bool) error {
debug.DebugMessage(debugFlag, "Checking filter validity for filter: "+name)
if filter == "" {
debug.DebugMessage(debugFlag, "Filter cannot be blank")
return errors.New("Error validating filter: Filter cannot be empty")
}

// Create a set of test tickets to work with and turn them into JSON
jsonListOfTickets, err := json.Marshal([]Ticket{
// Create test tickets
debug.DebugMessage(debugFlag, "Creating test tickets")
tickets := []Ticket{
{
ID: 1,
ID: 1,
Status: "new",
},
{
ID: 2,
ID: 2,
Status: "in progress",
},
{
ID: 3,
ID: 3,
Status: "closed",
},
})
if err != nil {
return err
}

// Turn jsonListOfTickets into a map[string]interface{}
var listOfTickets []Ticket
err = json.Unmarshal(jsonListOfTickets, &listOfTickets)
if err != nil {
return fmt.Errorf("Error unmarshalling jsonListOfTickets to validate filter: " + err.Error())
var listOfTickets []any
debug.DebugMessage(debugFlag, "Looping through tickets")
for _, ticket := range tickets {
debug.DebugMessage(debugFlag, "Adding iterTicket to listOfTickets")
ticketAsAny, err := ticket.ToAny()
if err != nil {
return err
}
listOfTickets = append(listOfTickets, ticketAsAny)
}

debug.DebugMessage(debugFlag, "Parsing jq query")
queryObj, err := gojq.Parse(filter)
if err != nil {
debug.DebugMessage(debugFlag, "Error in checkFilterIsValid while parsing filter: "+err.Error())
return fmt.Errorf("Filter validation error, unable to parse: " + err.Error())
}

// Just check that the filter can be used, we don't care about the result of
// the filter operation
iter := queryObj.Run(listOfTickets)
mapOfTickets := make(map[string]any)
mapOfTickets["tickets"] = listOfTickets
debug.DebugMessage(debugFlag, "Running jq query")
iter := queryObj.Run(mapOfTickets)
for {
debug.DebugMessage(debugFlag, "In checkFilterIsValid, queryObj.Run() loop")
result, ok := iter.Next()
if !ok {
debug.DebugMessage(debugFlag, "In checkFilterIsValid, queryObj.Run() nothig left")
break
}
debug.DebugMessage(debugFlag, "In checkFilterIsValid, passed iter.Next()")
if err, ok := result.(error); ok {
return fmt.Errorf("Filter validation error: " + err.Error())
debug.DebugMessage(debugFlag, "In checkFilterIsValid, iter.Next() returned error as result: "+err.Error())
if err, ok := err.(*gojq.HaltError); ok && err.Value() == nil {
debug.DebugMessage(debugFlag, "In checkFilterIsValid, queryObj.Run() returned nil")
break
}
return err
}
}

Expand Down Expand Up @@ -412,74 +429,100 @@ func GetFilter(filterName string, debugFlag bool) (Filter, error) {
if err != nil {
return Filter{}, err
}
// Does filters have key named filterName?
if _, ok := filters.Filters[filterName]; !ok {
return Filter{}, fmt.Errorf("Filter not found: " + filterName)
}
return filters.Filters[filterName], nil
}

// FilterTickets takes a list of tickets, a filter name, and a debug flag. It
// returns a list of tickets that match the filter. Returns an error if there
// is one.
func FilterTickets(tickets []Ticket, filterName string, debugFlag bool) (*[]Ticket, error) {
debug.DebugMessage(debugFlag, "Filtering tickets with filter: "+filterName+" on "+strconv.Itoa(len(tickets))+" tickets")
var listOfTicketsAsAny []any

// Get the filter
filter, err := GetFilter(filterName, debugFlag)
if err != nil {
debug.DebugMessage(debugFlag, "Error in FilterTickets while getting filter: "+err.Error())
return nil, err
}

// If there are no tickets, return
if len(tickets) == 0 {
return &[]Ticket{}, nil
}

// Parse the filter
queryObj, err := gojq.Parse(filter.Filter)
if err != nil {
debug.DebugMessage(debugFlag, "Error in FilterTickets while parsing filter: "+err.Error())
return nil, fmt.Errorf("Error parsing filter: " + err.Error())
}

// Convert []Ticket into []map[string]interface{} for gojq
var listOfTickets []map[string]interface{}
// Convert []Ticket for gojq
ticketsJSON, err := json.Marshal(tickets)
if err != nil {
debug.DebugMessage(debugFlag, "Error in FilterTickets while marshalling tickets: "+err.Error())
return nil, err
}
fmt.Println("The list of tickets as JSON: " + string(ticketsJSON))
err = json.Unmarshal(ticketsJSON, &listOfTickets)
debug.DebugMessage(debugFlag, "ticketsJSON: "+string(ticketsJSON))
err = json.Unmarshal(ticketsJSON, &listOfTicketsAsAny)
if err != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel there's way too much error handling. It interrupts the flow when reading the code. Is this a mandatory golang thing? In other languages, you just let exceptions bubble up to an exception handler.

debug.DebugMessage(debugFlag, "Error in FilterTickets while unmarshalling tickets: "+err.Error())
return nil, err
}
fmt.Println("The length of listOfTickets is " + strconv.Itoa(len(listOfTickets)))

// Apply the filter
iter := queryObj.Run(listOfTickets)
var iterTicket Ticket
var filteredTickets []Ticket
mapOfTickets := map[string]any{"tickets": listOfTicketsAsAny}
iter := queryObj.Run(mapOfTickets)

// Print mapOfTickets
debug.DebugMessage(debugFlag, "mapOfTickets: "+fmt.Sprint(mapOfTickets))

// Convert back to []Ticket
debug.DebugMessage(debugFlag, "FilterTickets Starting filter loop")
var filteredTicketsAsAny []any
for {
debug.DebugMessage(debugFlag, "Starting filter loop iteration")
result, ok := iter.Next()
if !ok {
debug.DebugMessage(debugFlag, "Finished filter loop, nothing left.")
break
}
debug.DebugMessage(debugFlag, "FilterTickets Next returned: "+fmt.Sprint(result))
if err, ok := result.(error); ok {
return nil, fmt.Errorf("Error applying filter: " + err.Error())
}
// Turn result into JSON and then into Ticket
resultJSON, err := json.Marshal(result)
if err != nil {
return nil, err
}
fmt.Println("Trying to unmarshal: " + string(resultJSON))
err = json.Unmarshal(resultJSON, &iterTicket)
if err != nil {
debug.DebugMessage(debugFlag, "iter.Next() returned an error: "+err.Error())
if err, ok := err.(*gojq.HaltError); ok && err.Value() == nil {
debug.DebugMessage(debugFlag, "In checkFilterIsValid, queryObj.Run() returned nil")
break
}
return nil, err
}
filteredTickets = append(filteredTickets, iterTicket)
filteredTicketsAsAny = append(filteredTicketsAsAny, result)
}
debug.DebugMessage(debugFlag, "Finished filter loop")

return &filteredTickets, nil
}
var filteredTickets []Ticket
if len(filteredTicketsAsAny) == 0 {
return &[]Ticket{}, nil
}

// GetCurrentFilter takes a debug flag and returns the name of the current
// filter. Returns an error if there is one. This function may return an empty
// string if the current filter has not yet been set.
func GetCurrentFilter(debugFlag bool) (string, error) {
debug.DebugMessage(debugFlag, "GetCurrentFilter() start")
filters, err := GetFilters(common.BranchName, debugFlag)
filteredTicketsAsJSON, err := json.Marshal(filteredTicketsAsAny)
if err != nil {
return "", err
debug.DebugMessage(debugFlag, "Error in FilterTickets while marshalling filteredTicketsAsInterfaces: "+err.Error())
return nil, err
}
return filters.CurrentFilter, nil

// filteredTicketsAsJSON is a [][]interface{} at this point, we only want
// the first element of the top level slice
debug.DebugMessage(debugFlag, "Unmarshalling filteredTicketsAsJSON "+string(filteredTicketsAsJSON))
err = json.Unmarshal(filteredTicketsAsJSON, &filteredTickets)
if err != nil {
debug.DebugMessage(debugFlag, "Error in FilterTickets while unmarshalling filteredTicketsAsInterfaces: "+err.Error())
return nil, err
}
return &filteredTickets, nil
}
23 changes: 5 additions & 18 deletions pkg/ticket/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import (
"gopkg.in/yaml.v2"
)

func HandleList(w io.Writer, windowWidth int, branchName string, filterName string, filterSet bool, debugFlag bool) error {
func HandleList(w io.Writer, windowWidth int, branchName string, filterName string, debugFlag bool) error {
debug.DebugMessage(debugFlag, "Opening git repository")
thisRepo, err := git.OpenRepository(".")
if err != nil {
return err
}

output, err := ListTickets(
thisRepo, branchName, windowWidth, filterName, filterSet, debugFlag)
thisRepo, branchName, windowWidth, filterName, debugFlag)
if err != nil {
return err
}
Expand All @@ -39,36 +39,23 @@ func GetTicketsList() ([]Ticket, error) {
return GetListOfTickets(thisRepo, common.BranchName, false)
}

func ListTickets(thisRepo *git.Repository, branchName string, windowWidth int, filterName string, filterSet bool, debugFlag bool) (string, error) {
func ListTickets(thisRepo *git.Repository, branchName string, windowWidth int, filterName string, debugFlag bool) (string, error) {
output := ""

// Get a list of tickets from the repo
var ticketsList []Ticket
ticketsList, err := GetListOfTickets(thisRepo, branchName, debugFlag)
if err != nil {
debug.DebugMessage(debugFlag, "Unable to list tickets: "+err.Error())
return "", fmt.Errorf("Unable to list tickets: %s", err) // TODO: err
}

// Sanity check that a filter has been set before attempting to set
// preferred filter
currentFilter, err := GetCurrentFilter(debugFlag)
if err != nil {
return "", err
}
if filterName == "" && filterSet {
return "", fmt.Errorf("Cannot set preferred filter when no filter has been configured yet, create one with the filter subcommand.")
}

// Filter tickets
filteredTicketsList := new([]Ticket)
if filterName != "" {
filteredTicketsList, err = FilterTickets(ticketsList, filterName, debugFlag)
if err != nil {
return "", err
}
} else if currentFilter != "" {
filteredTicketsList, err = FilterTickets(ticketsList, currentFilter, debugFlag)
if err != nil {
debug.DebugMessage(debugFlag, "Unable to filter tickets with filterName: "+err.Error())
return "", err
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion pkg/ticket/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestHandleList(t *testing.T) {
w := &strings.Builder{}

// list tickets
err := HandleList(testCase.debugFlag, testCase.branchName, 0, w)
err := HandleList(w, 0, testCase.branchName, "", testCase.debugFlag)
if err != nil {
t.Fatal(err)
}
Expand Down
33 changes: 33 additions & 0 deletions pkg/ticket/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,36 @@ func (t *Ticket) TicketToYaml() []byte {
func PrintParameterMissing(param string) {
fmt.Printf("A required parameter was not provided, check the '--help' output for the action for more details. Missing parameter: %s\n", param)
}

// ToAny() Converts the ticket into a series of map[string]any and []any for the
// purpose of filtering by gojq.
func (t *Ticket) ToAny() (map[string]any, error) {
ticketAsAny := map[string]any{
"title": t.Title,
"description": t.Description,
"priority": t.Priority,
"severity": t.Severity,
"status": t.Status,
"next_comment_id": t.NextCommentID,
"id": t.ID,
"created": t.Created,
}
var commentMaps []map[string]interface{}
for _, comment := range t.Comments {
commentMap := make(map[string]interface{})
commentMap["ID"] = comment.ID
commentMap["Created"] = comment.Created
commentMap["Body"] = comment.Body
commentMap["Author"] = comment.Author
commentMaps = append(commentMaps, commentMap)
}

ticketAsAny["comments"] = commentMaps

var labelsAny []any
for _, label := range t.Labels {
labelsAny = append(labelsAny, label)
}
ticketAsAny["labels"] = labelsAny
return ticketAsAny, nil
}