diff --git a/app/api_topologies.go b/app/api_topologies.go index 4d0a38e2f..564216293 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -5,8 +5,10 @@ import ( "net/http" "net/url" "sort" + "strings" "sync" + log "github.com/Sirupsen/logrus" "github.com/gorilla/mux" "golang.org/x/net/context" @@ -295,6 +297,59 @@ type APITopologyOptionGroup struct { ID string `json:"id"` Default string `json:"defaultValue,omitempty"` Options []APITopologyOption `json:"options,omitempty"` + // SelectType describes how options can be picked. Currently defined values: + // "one": Default if empty. Exactly one option may be picked from the list. + // "union": Any number of options may be picked. Nodes matching any option filter selected are displayed. + // Value and Default should be a ","-seperated list. + SelectType string `json:"selectType,omitempty"` +} + +// Get the render filters to use for this option group as a Decorator, if any. +// If second arg is false, no decorator was needed. +func (g APITopologyOptionGroup) getFilterDecorator(value string) (render.Decorator, bool) { + if value == "" { + value = g.Default + } + selectType := g.SelectType + if selectType == "" { + selectType = "one" + } + var values []string + switch selectType { + case "one": + values = []string{value} + case "union": + values = strings.Split(value, ",") + default: + log.Errorf("Invalid select type %s for option group %s, ignoring option", selectType, g.ID) + return nil, false + } + filters := []render.FilterFunc{} + for _, opt := range g.Options { + for _, v := range values { + if v != opt.Value { + continue + } + var filter render.FilterFunc + if opt.filter == nil { + // No filter means match everything (pseudo doesn't matter) + filter = func(n report.Node) bool { return true } + } else if opt.filterPseudo { + // Apply filter to pseudo topologies also + filter = opt.filter + } else { + // Allow all pseudo topology nodes, only apply filter to non-pseudo + filter = render.AnyFilterFunc(render.IsPseudoTopology, opt.filter) + } + filters = append(filters, filter) + } + } + if len(filters) == 0 { + return nil, false + } + // Since we've encoded whether to ignore pseudo topologies into each subfilter, + // we want no special behaviour for pseudo topologies here, which corresponds to MakePseudo + return render.MakeFilterPseudoDecorator(render.AnyFilterFunc(filters...)), true } // APITopologyOption describes a ¶m=value to a given topology. @@ -437,17 +492,8 @@ func (r *Registry) RendererForTopology(topologyID string, values url.Values, rpt var decorators []render.Decorator for _, group := range topology.Options { value := values.Get(group.ID) - for _, opt := range group.Options { - if opt.filter == nil { - continue - } - if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) { - if opt.filterPseudo { - decorators = append(decorators, render.MakeFilterPseudoDecorator(opt.filter)) - } else { - decorators = append(decorators, render.MakeFilterDecorator(opt.filter)) - } - } + if decorator, ok := group.getFilterDecorator(value); ok { + decorators = append(decorators, decorator) } } if len(decorators) > 0 { diff --git a/render/filters.go b/render/filters.go index fc5425d46..f7df8d337 100644 --- a/render/filters.go +++ b/render/filters.go @@ -321,6 +321,12 @@ func IsNotPseudo(n report.Node) bool { return n.Topology != Pseudo || strings.HasSuffix(n.ID, TheInternetID) || strings.HasPrefix(n.ID, ServiceNodeIDPrefix) } +// IsPseudoTopology returns true if the node is in a pseudo topology, +// mimicing the check performed by MakeFilter() instead of the more complex check in IsNotPseudo() +func IsPseudoTopology(n report.Node) bool { + return n.Topology == Pseudo +} + // IsNamespace checks if the node is a pod/service in the specified namespace func IsNamespace(namespace string) FilterFunc { return func(n report.Node) bool {