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 {
|
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.
|
// Produces one or more exchange contact scopes.
|
||||||
@ -288,6 +291,8 @@ func makeExchangeFilterScope(cat, filterCat exchangeCategory, vs []string) Excha
|
|||||||
scopeKeyGranularity: Filter,
|
scopeKeyGranularity: Filter,
|
||||||
scopeKeyCategory: cat.String(),
|
scopeKeyCategory: cat.String(),
|
||||||
scopeKeyInfoFilter: filterCat.String(),
|
scopeKeyInfoFilter: filterCat.String(),
|
||||||
|
scopeKeyResource: Filter,
|
||||||
|
scopeKeyDataType: cat.dataType(),
|
||||||
filterCat.String(): join(vs...),
|
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)
|
// Granularity describes the granularity (directory || item)
|
||||||
// of the data in scope
|
// of the data in scope
|
||||||
func (s ExchangeScope) Granularity() string {
|
func (s ExchangeScope) Granularity() string {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package selectors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -21,6 +22,8 @@ const (
|
|||||||
scopeKeyCategory = "category"
|
scopeKeyCategory = "category"
|
||||||
scopeKeyGranularity = "granularity"
|
scopeKeyGranularity = "granularity"
|
||||||
scopeKeyInfoFilter = "info_filter"
|
scopeKeyInfoFilter = "info_filter"
|
||||||
|
scopeKeyResource = "resource"
|
||||||
|
scopeKeyDataType = "type"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The granularity exprerssed by the scope. Groups imply non-item granularity,
|
// 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.
|
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.
|
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.
|
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.
|
// 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
|
// Destination
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -1,12 +1,41 @@
|
|||||||
package selectors
|
package selectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"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 {
|
type SelectorSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
}
|
}
|
||||||
@ -32,3 +61,93 @@ func (suite *SelectorSuite) TestExistingDestinationErr() {
|
|||||||
err := existingDestinationErr("foo", "bar")
|
err := existingDestinationErr("foo", "bar")
|
||||||
assert.Error(suite.T(), err)
|
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