Skip to content

Commit

Permalink
Analytics: implement impact analysis
Browse files Browse the repository at this point in the history
This analysis provides inside into which layer/document get affected if
given set of keys is 'touched'

Signed-off-by: Richard Kosegi <richard.kosegi@gmail.com>
  • Loading branch information
rkosegi committed May 15, 2024
1 parent 10d3b7c commit 80e5094
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 20 deletions.
13 changes: 13 additions & 0 deletions analytics/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ limitations under the License.

package analytics

import (
"github.com/rkosegi/yaml-toolkit/dom"
"github.com/stretchr/testify/assert"
"strings"
"testing"
)

type testDocsData struct {
doc string
tags []string
Expand Down Expand Up @@ -77,3 +84,9 @@ defaults:
},
}
)

func loadDocsIntoSet(t *testing.T, ds DocumentSet) {
for k, v := range testDocSrc {
assert.NoError(t, ds.AddDocumentFromReader(k, strings.NewReader(v.doc), dom.DefaultYamlDecoder, WithTags(v.tags...)))
}
}
20 changes: 0 additions & 20 deletions analytics/dependency_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,6 @@ import (
"testing"
)

func srcDoc(t *testing.T) dom.ContainerBuilder {
c, err := b.FromReader(strings.NewReader(`
---
server:
port: ${env.server.port}
client:
url: ${env.client.url}
default:
port: 8080
`), dom.DefaultYamlDecoder)
assert.NoError(t, err)
return c
}

func loadDocsIntoSet(t *testing.T, ds DocumentSet) {
for k, v := range testDocSrc {
assert.NoError(t, ds.AddDocumentFromReader(k, strings.NewReader(v.doc), dom.DefaultYamlDecoder, WithTags(v.tags...)))
}
}

func TestDefaultDependencyResolver(t *testing.T) {
ds := NewDocumentSet()
loadDocsIntoSet(t, ds)
Expand Down
88 changes: 88 additions & 0 deletions analytics/impact_analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2024 Richard Kosegi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package analytics

import (
"github.com/rkosegi/yaml-toolkit/dom"
)

type ImpactAnalysis interface {
ResolveOverlayDocument(od dom.OverlayDocument, keys []string) map[string]dom.Coordinates
ResolveDocumentSet(ds DocumentSet, keys []string) map[string]dom.Coordinates
}

type impactAnalysis struct {
keyFilterFn StringPredicateFn
placeholderMatcherFn func(string) dom.SearchValueFunc
}

func (i *impactAnalysis) ResolveDocumentSet(ds DocumentSet, keys []string) map[string]dom.Coordinates {
return i.ResolveOverlayDocument(ds.AsOne(), keys)
}

func (i *impactAnalysis) resolveKey(od dom.OverlayDocument, key string) dom.Coordinates {
if x := od.Search(i.placeholderMatcherFn(key)); x != nil {
return x
}
return nil
}

func (i *impactAnalysis) ResolveOverlayDocument(od dom.OverlayDocument, keys []string) map[string]dom.Coordinates {
res := make(map[string]dom.Coordinates)
for _, key := range keys {
r := i.resolveKey(od, key)
if r != nil {
res[key] = r
}
}
return res
}

type ImpactAnalysisBuilder interface {
Build() ImpactAnalysis
// WithKeyFilter allows to override default key filter predicate
WithKeyFilter(StringPredicateFn) ImpactAnalysisBuilder
}

type impactAnalysisBuilder struct {
// method to override dom.SearchValueFunc which searches property value for presence of placeholder reference.
placeholderMatcherFn func(string) dom.SearchValueFunc
keyFilterFn StringPredicateFn
}

func (iab *impactAnalysisBuilder) WithKeyFilter(keyFilterFn StringPredicateFn) ImpactAnalysisBuilder {
iab.keyFilterFn = keyFilterFn
return iab
}

// Build builds new instance of ImpactAnalysis using current state of builder.
// It's safe to call any builder method after Build() has been called, it won't affect existing builders,
// only newly created called afterward
func (iab *impactAnalysisBuilder) Build() ImpactAnalysis {
return &impactAnalysis{
placeholderMatcherFn: iab.placeholderMatcherFn,
keyFilterFn: iab.keyFilterFn,
}
}

// NewImpactAnalysisBuilder returns new instance of ImpactAnalysisBuilder with default values set
func NewImpactAnalysisBuilder() ImpactAnalysisBuilder {
return &impactAnalysisBuilder{
placeholderMatcherFn: hasPlaceholderFunc,
keyFilterFn: matchAll,
}
}
39 changes: 39 additions & 0 deletions analytics/impact_analysis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright 2024 Richard Kosegi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package analytics

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestAnalyseImpact(t *testing.T) {
ds := NewDocumentSet()
loadDocsIntoSet(t, ds)
ia := NewImpactAnalysisBuilder().
WithKeyFilter(matchAll).
Build()

res := ia.ResolveDocumentSet(ds, []string{
"env.port",
"defaults.connection.retryCount",
"unknown.invalid.key",
})

assert.Equal(t, 2, len(res))
assert.Equal(t, 1, len(res["env.port"]))
}

0 comments on commit 80e5094

Please sign in to comment.