Skip to content

Other router types #788

Open
Open
@rowanseymour

Description

@rowanseymour

We previously had a two other router types in the engine, which were removed during a refactor, so stashing their code here for future use/discussion.

router which always picks the first exit:

package routers

import (
    "github.com/nyaruka/goflow/flows"
    "github.com/nyaruka/goflow/utils"
)

func init() {
    RegisterType(TypeFirst, func() flows.Router { return &FirstRouter{} })
}

// TypeFirst is the type for FirstRouters
const TypeFirst string = "first"

// FirstRouter is a simple router that always takes the first exit
type FirstRouter struct {
    BaseRouter
}

func NewFirstRouter(resultName string) *FirstRouter {
    return &FirstRouter{BaseRouter: newBaseRouter(TypeFirst, resultName)}
}

// Validate validates the arguments on this router
func (r *FirstRouter) Validate(exits []flows.Exit) error {
    return utils.Validate(r)
}

// PickRoute always picks the first exit if available for this router
func (r *FirstRouter) PickRoute(run flows.FlowRun, exits []flows.Exit, step flows.Step) (*string, flows.Route, error) {
    if len(exits) == 0 {
        return nil, flows.NoRoute, nil
    }

    return nil, flows.NewRoute(exits[0].UUID(), "", nil), nil
}

// Inspect inspects this object and any children
func (r *FirstRouter) Inspect(inspect func(flows.Inspectable)) {
    inspect(r)
}

random router which remembers which exits it took previously and doesn't pick the same more than once:

package routers

import (
    "github.com/nyaruka/goflow/flows"
    "github.com/nyaruka/goflow/utils"

    "github.com/pkg/errors"
    "github.com/shopspring/decimal"
)

func init() {
    RegisterType(TypeRandomOnce, func() flows.Router { return &RandomOnceRouter{} })
}

// TypeRandomOnce is the constant for our random once router
const TypeRandomOnce string = "random_once"

// RandomOnceRouter exits of our exits once (randomly) before taking exit
type RandomOnceRouter struct {
    BaseRouter
    Default flows.CategoryUUID `json:"default_category_uuid" validate:"required,uuid4"`
}

// NewRandomOnceRouter creates a new random-once router
func NewRandomOnceRouter(defaultExit flows.ExitUUID, resultName string) *RandomOnceRouter {
    return &RandomOnceRouter{
        BaseRouter: newBaseRouter(TypeRandomOnce, resultName),
        Default:    defaultExit,
    }
}

// Validate validates the parameters on this router
func (r *RandomOnceRouter) Validate(exits []flows.Exit) error {
    // check the default category is valid
    if r.Default != "" && !r.isValidCategory(r.Default) {
        return errors.Errorf("default category %s is not a valid category", r.Default)
    }

    return r.validate(exits)
}

// PickRoute will attempt to take a random exit it hasn't taken before. If all exits have been taken, then it will
// take the exit specified in it's Exit parameter
func (r *RandomOnceRouter) PickRoute(run flows.FlowRun, exits []flows.Exit, step flows.Step) (*string, flows.Route, error) {
    if len(exits) == 0 {
        return nil, flows.NoRoute, nil
    }

    // find all the exits we have taken
    takenBefore := make(map[flows.ExitUUID]bool)
    for _, s := range run.Path() {
        if s.NodeUUID() == step.NodeUUID() {
            takenBefore[s.ExitUUID()] = true
        }
    }

    // build up a list of the valid exits
    var validExits []flows.ExitUUID
    for i := range exits {
        // this isn't our default exit and we haven't taken it yet
        if exits[i].UUID() != r.Default && !takenBefore[exits[i].UUID()] {
            validExits = append(validExits, exits[i].UUID())
        }
    }

    // no valid choices? exit!
    if len(validExits) == 0 {
        return nil, flows.NewRoute(r.Default, "", nil), nil
    }

    // ok, now pick one randomly
    rand := utils.RandDecimal()
    exitNum := rand.Mul(decimal.New(int64(len(validExits)), 0)).IntPart()
    return nil, flows.NewRoute(validExits[exitNum], rand.String(), nil), nil
}

// Inspect inspects this object and any children
func (r *RandomOnceRouter) Inspect(inspect func(flows.Inspectable)) {
    inspect(r)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions