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

Other router types #788

Open
rowanseymour opened this issue Oct 29, 2019 · 3 comments
Open

Other router types #788

rowanseymour opened this issue Oct 29, 2019 · 3 comments

Comments

@rowanseymour
Copy link
Member

rowanseymour commented Oct 29, 2019

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)
}
@nicpottier
Copy link
Collaborator

The second of these is actually a pretty useful for anyone who wants to do more scientific polling that randomizes question ordering. (this is a feature on some other platforms)

@rowanseymour
Copy link
Member Author

Yeah - 5.4 feature?

@rowanseymour
Copy link
Member Author

Just mentioning here that a similar usecase came up recently that wouldn't have been satisfied by this solution because the random questions were being sent weekly and wouldn't have been in the same session. For that you would need a way to track questions answered via a contact field and randomly pop new questions from that. Having said that, the more common usecase is probably shuffling questions within the same session.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants