introduce printable version of selectors (#424)
This commit is contained in:
parent
e50728c0d6
commit
cc810fa3ab
@ -168,7 +168,10 @@ func makeExchangeScope(granularity string, cat exchangeCategory, vs []string) Ex
|
||||
}
|
||||
|
||||
func makeExchangeUserScope(user, granularity string, cat exchangeCategory, vs []string) ExchangeScope {
|
||||
return makeExchangeScope(granularity, cat, vs).set(ExchangeUser, user)
|
||||
es := makeExchangeScope(granularity, cat, vs).set(ExchangeUser, user)
|
||||
es[scopeKeyResource] = user
|
||||
es[scopeKeyDataType] = cat.dataType()
|
||||
return es
|
||||
}
|
||||
|
||||
// Produces one or more exchange contact scopes.
|
||||
@ -288,6 +291,8 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha
|
||||
scopeKeyGranularity: Filter,
|
||||
scopeKeyCategory: cat.String(),
|
||||
scopeKeyInfoFilter: filterCat.String(),
|
||||
scopeKeyResource: Filter,
|
||||
scopeKeyDataType: cat.dataType(),
|
||||
filterCat.String(): join(vs...),
|
||||
}
|
||||
}
|
||||
@ -436,6 +441,19 @@ func exchangeCatAtoI(s string) exchangeCategory {
|
||||
}
|
||||
}
|
||||
|
||||
// exchangeDataType returns the human-readable name of the core data type.
|
||||
// Ex: ExchangeContactFolder.dataType() => ExchangeContact.String()
|
||||
// Ex: ExchangeEvent.dataType() => ExchangeEvent.String().
|
||||
func (ec exchangeCategory) dataType() string {
|
||||
switch ec {
|
||||
case ExchangeContact, ExchangeContactFolder:
|
||||
return ExchangeContact.String()
|
||||
case ExchangeMail, ExchangeMailFolder:
|
||||
return ExchangeMail.String()
|
||||
}
|
||||
return ec.String()
|
||||
}
|
||||
|
||||
// Granularity describes the granularity (directory || item)
|
||||
// of the data in scope
|
||||
func (s ExchangeScope) Granularity() string {
|
||||
|
||||
@ -2,6 +2,7 @@ package selectors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -21,6 +22,8 @@ const (
|
||||
scopeKeyCategory = "category"
|
||||
scopeKeyGranularity = "granularity"
|
||||
scopeKeyInfoFilter = "info_filter"
|
||||
scopeKeyResource = "resource"
|
||||
scopeKeyDataType = "type"
|
||||
)
|
||||
|
||||
// The granularity exprerssed by the scope. Groups imply non-item granularity,
|
||||
@ -60,7 +63,7 @@ type Selector struct {
|
||||
Service service `json:"service,omitempty"` // The service scope of the data. Exchange, Teams, Sharepoint, etc.
|
||||
Excludes []map[string]string `json:"exclusions,omitempty"` // A slice of exclusion scopes. Exclusions apply globally to all inclusions/filters, with any-match behavior.
|
||||
Filters []map[string]string `json:"filters,omitempty"` // A slice of filter scopes. All inclusions must also match ALL filters.
|
||||
Includes []map[string]string `json:"scopes,omitempty"` // A slice of inclusion scopes. Comparators must match either one of these, or all filters, to be included.
|
||||
Includes []map[string]string `json:"includes,omitempty"` // A slice of inclusion scopes. Comparators must match either one of these, or all filters, to be included.
|
||||
}
|
||||
|
||||
// helper for specific selector instance constructors.
|
||||
@ -148,6 +151,94 @@ func appendIncludes[T baseScope](
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Printing Selectors for Human Reading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type Printable struct {
|
||||
Service string `json:"service"`
|
||||
Excludes map[string][]string `json:"excludes,omitempty"`
|
||||
Filters map[string][]string `json:"filters,omitempty"`
|
||||
Includes map[string][]string `json:"includes,omitempty"`
|
||||
}
|
||||
|
||||
// Printable is the minimized display of a selector, formatted for human readability.
|
||||
// This transformer assumes that the scopeKeyResource and scopeKeyDataType have been
|
||||
// added to all scopes as they were created. It is unable to infer resource or data
|
||||
// type values from existing scope values.
|
||||
func (s Selector) Printable() Printable {
|
||||
return Printable{
|
||||
Service: s.Service.String(),
|
||||
Excludes: toResourceTypeMap(s.Excludes),
|
||||
Filters: toResourceTypeMap(s.Filters),
|
||||
Includes: toResourceTypeMap(s.Includes),
|
||||
}
|
||||
}
|
||||
|
||||
// Resources generates a tabular-readable output of the resources in Printable.
|
||||
// Only the first (arbitrarily picked) resource is displayed. All others are
|
||||
// simply counted. If no inclusions exist, uses Filters. If no filters exist,
|
||||
// defaults to "All".
|
||||
// Resource refers to the top-level entity in the service. User for Exchange,
|
||||
// Site for sharepoint, etc.
|
||||
func (p Printable) Resources() string {
|
||||
s := resourcesShortFormat(p.Includes)
|
||||
if len(s) == 0 {
|
||||
s = resourcesShortFormat(p.Filters)
|
||||
}
|
||||
if len(s) == 0 {
|
||||
s = "All"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// returns a string with the resources in the map. Shortened to the first resource key,
|
||||
// plus, if more exist, " (len-1 more)"
|
||||
func resourcesShortFormat(m map[string][]string) string {
|
||||
var s string
|
||||
for k := range m {
|
||||
s = k
|
||||
break
|
||||
}
|
||||
if len(s) > 0 && len(m) > 1 {
|
||||
s = fmt.Sprintf("%s (%d more)", s, len(m)-1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Transforms the slice to a single map.
|
||||
// Keys are each map's scopeKeyResource value.
|
||||
// Values are the set of all scopeKeyDataTypes for a given resource.
|
||||
func toResourceTypeMap(ms []map[string]string) map[string][]string {
|
||||
if len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make(map[string][]string)
|
||||
for _, m := range ms {
|
||||
res := m[scopeKeyResource]
|
||||
if res == AnyTgt {
|
||||
res = "All"
|
||||
}
|
||||
r[res] = addToSet(r[res], m[scopeKeyDataType])
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// returns [v] if set is empty,
|
||||
// returns self if set contains v,
|
||||
// appends v to self, otherwise.
|
||||
func addToSet(set []string, v string) []string {
|
||||
if len(set) == 0 {
|
||||
return []string{v}
|
||||
}
|
||||
for _, s := range set {
|
||||
if s == v {
|
||||
return set
|
||||
}
|
||||
}
|
||||
return append(set, v)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Destination
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -1,12 +1,41 @@
|
||||
package selectors
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
user = "me@my.onmicrosoft.com"
|
||||
)
|
||||
|
||||
var (
|
||||
dataType = ExchangeEvent.String()
|
||||
)
|
||||
|
||||
func stubScope() map[string]string {
|
||||
return map[string]string{
|
||||
ExchangeEvent.String(): AnyTgt,
|
||||
ExchangeUser.String(): user,
|
||||
scopeKeyCategory: dataType,
|
||||
scopeKeyGranularity: Group,
|
||||
scopeKeyResource: user,
|
||||
scopeKeyDataType: dataType,
|
||||
}
|
||||
}
|
||||
|
||||
func stubSelector() Selector {
|
||||
return Selector{
|
||||
Service: ServiceExchange,
|
||||
Excludes: []map[string]string{stubScope()},
|
||||
Filters: []map[string]string{stubScope()},
|
||||
Includes: []map[string]string{stubScope()},
|
||||
}
|
||||
}
|
||||
|
||||
type SelectorSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
@ -32,3 +61,93 @@ func (suite *SelectorSuite) TestExistingDestinationErr() {
|
||||
err := existingDestinationErr("foo", "bar")
|
||||
assert.Error(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *SelectorSuite) TestPrintable() {
|
||||
t := suite.T()
|
||||
|
||||
sel := stubSelector()
|
||||
p := sel.Printable()
|
||||
|
||||
assert.Equal(t, sel.Service.String(), p.Service)
|
||||
assert.Equal(t, 1, len(p.Excludes))
|
||||
assert.Equal(t, 1, len(p.Filters))
|
||||
assert.Equal(t, 1, len(p.Includes))
|
||||
}
|
||||
|
||||
func (suite *SelectorSuite) TestPrintable_IncludedResources() {
|
||||
t := suite.T()
|
||||
|
||||
sel := stubSelector()
|
||||
p := sel.Printable()
|
||||
res := p.Resources()
|
||||
|
||||
assert.Equal(t, user, res, "resource should state only the user")
|
||||
|
||||
sel.Includes = []map[string]string{
|
||||
stubScope(),
|
||||
{scopeKeyResource: "smarf", scopeKeyDataType: dataType},
|
||||
{scopeKeyResource: "smurf", scopeKeyDataType: dataType}}
|
||||
p = sel.Printable()
|
||||
res = p.Resources()
|
||||
|
||||
assert.True(t, strings.HasSuffix(res, "(2 more)"), "resource '"+res+"' should have (2 more) suffix")
|
||||
|
||||
p.Includes = nil
|
||||
res = p.Resources()
|
||||
|
||||
assert.Equal(t, user, res, "resource on filters should state only the user")
|
||||
|
||||
p.Filters = nil
|
||||
res = p.Resources()
|
||||
|
||||
assert.Equal(t, "All", res, "resource with no Includes or Filters should state All")
|
||||
}
|
||||
|
||||
func (suite *SelectorSuite) TestToResourceTypeMap() {
|
||||
table := []struct {
|
||||
name string
|
||||
input []map[string]string
|
||||
expect map[string][]string
|
||||
}{
|
||||
{
|
||||
name: "single scope",
|
||||
input: []map[string]string{stubScope()},
|
||||
expect: map[string][]string{
|
||||
user: {dataType},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disjoint resources",
|
||||
input: []map[string]string{
|
||||
stubScope(),
|
||||
{
|
||||
scopeKeyResource: "smarf",
|
||||
scopeKeyDataType: dataType,
|
||||
},
|
||||
},
|
||||
expect: map[string][]string{
|
||||
user: {dataType},
|
||||
"smarf": {dataType},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disjoint types",
|
||||
input: []map[string]string{
|
||||
stubScope(),
|
||||
{
|
||||
scopeKeyResource: user,
|
||||
scopeKeyDataType: "other",
|
||||
},
|
||||
},
|
||||
expect: map[string][]string{
|
||||
user: {dataType, "other"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
rtm := toResourceTypeMap(test.input)
|
||||
assert.Equal(t, test.expect, rtm)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user