Skip to content

Commit 2565db3

Browse files
authored
Merge pull request #24 from Mertcikla/enricher-registry
Enricher registry
2 parents dcf3b15 + d821ed0 commit 2565db3

122 files changed

Lines changed: 10969 additions & 493 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/analyze/analyze.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ func resolveAnalyzeWatchSettings(cfg *workspace.Config, languages []string, maxE
175175
MaxOutgoingPerElement: cfg.Watch.Thresholds.MaxOutgoingPerElement,
176176
MaxExpandedConnectorsPerGroup: cfg.Watch.Thresholds.MaxExpandedConnectorsPerGroup,
177177
}
178+
settings.Visibility = watchpkg.VisibilityConfig{
179+
CoreThresholdEnabled: cfg.Watch.Visibility.CoreThresholdEnabled,
180+
CoreThreshold: cfg.Watch.Visibility.CoreThreshold,
181+
TierMultiplier: cfg.Watch.Visibility.TierMultiplier,
182+
MaxExpansionMultiplier: cfg.Watch.Visibility.MaxExpansionMultiplier,
183+
CoreThresholdSet: true,
184+
WeightsSet: true,
185+
Weights: watchpkg.VisibilityWeights{
186+
Changed: cfg.Watch.Visibility.Weights.Changed,
187+
Selected: cfg.Watch.Visibility.Weights.Selected,
188+
UserShow: cfg.Watch.Visibility.Weights.UserShow,
189+
UserHide: cfg.Watch.Visibility.Weights.UserHide,
190+
HighSignalFact: cfg.Watch.Visibility.Weights.HighSignalFact,
191+
RelationshipProximity: cfg.Watch.Visibility.Weights.RelationshipProximity,
192+
DependencyFact: cfg.Watch.Visibility.Weights.DependencyFact,
193+
UtilityNoise: cfg.Watch.Visibility.Weights.UtilityNoise,
194+
HighDegreeNoise: cfg.Watch.Visibility.Weights.HighDegreeNoise,
195+
},
196+
}
178197
}
179198
if len(languages) > 0 {
180199
settings.Languages = languages

cmd/analyze/analyze_test.go

Lines changed: 233 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"os"
66
"path/filepath"
7+
"strings"
78
"testing"
89

910
"github.com/mertcikla/tld/cmd"
@@ -33,6 +34,20 @@ func TestAnalyzeCmd_WatchPipelineWritesYAML(t *testing.T) {
3334
t.Fatalf("symbol %q (%s) has no placement", element.Name, ref)
3435
}
3536
}
37+
38+
stdout, stderr, err = cmd.RunCmd(t, dir, "analyze", repoDir, "--data-dir", dataDir, "--embedding-provider", "none")
39+
if err != nil {
40+
t.Fatalf("second analyze: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
41+
}
42+
ws, err = workspace.Load(dir)
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
for _, element := range ws.Elements {
47+
if element.Kind == "file" && strings.HasPrefix(element.FilePath, ".tld/") {
48+
t.Fatalf("generated workspace YAML should not be scanned as source: %+v", element)
49+
}
50+
}
3651
}
3752

3853
func TestAnalyzeCmd_RuntimeArtifactsUseArchitectureView(t *testing.T) {
@@ -108,15 +123,34 @@ spec:
108123
if err != nil {
109124
t.Fatal(err)
110125
}
111-
if countKind(ws, "function") != 0 || countKind(ws, "file") != 0 || countKind(ws, "folder") != 0 {
112-
t.Fatalf("architecture view should hide code/file noise: %+v", ws.Elements)
126+
architectureRef := refByElementName(ws, "Architecture")
127+
structuralRef := refByElementName(ws, "Structural")
128+
repositoryRef := refByKind(ws, "repository")
129+
if architectureRef == "" || structuralRef == "" || repositoryRef == "" {
130+
t.Fatalf("missing repository sections: %+v", ws.Elements)
131+
}
132+
if !hasPlacementParent(ws, architectureRef, repositoryRef) || !hasPlacementParent(ws, structuralRef, repositoryRef) {
133+
t.Fatalf("architecture and structural sections should be siblings under repository: %+v", ws.Elements)
113134
}
114135
for _, name := range []string{"alpha", "beta", "cache", "External traffic"} {
115-
if refByElementName(ws, name) == "" {
136+
ref := refByElementNameWithParent(ws, name, architectureRef)
137+
if ref == "" {
116138
t.Fatalf("missing architecture element %q in %+v", name, ws.Elements)
117139
}
118140
}
119-
if !connectorByElementNames(ws, "alpha", "beta") || !connectorByElementNames(ws, "alpha", "cache") || !connectorByElementNames(ws, "External traffic", "alpha") {
141+
deployRef := refByElementName(ws, "deploy")
142+
if deployRef == "" || !hasPlacementParent(ws, deployRef, structuralRef) {
143+
t.Fatalf("top-level structural folder should be under Structural, ref=%q elements=%+v", deployRef, ws.Elements)
144+
}
145+
if ref := refByElementName(ws, "topology.yaml"); ref == "" || !hasPlacementParent(ws, ref, deployRef) {
146+
t.Fatalf("structural file should be under its folder view, ref=%q elements=%+v", ref, ws.Elements)
147+
}
148+
alphaRef := refByElementNameWithParent(ws, "alpha", architectureRef)
149+
topologyRef := refByElementName(ws, "topology.yaml")
150+
if alphaRef == "" || topologyRef == "" || !ws.Elements[alphaRef].HasView || !hasPlacementParent(ws, topologyRef, alphaRef) {
151+
t.Fatalf("bound architecture component should own a deep-dive view with structural targets: alpha=%q topology=%q %+v", alphaRef, topologyRef, ws.Elements)
152+
}
153+
if !connectorByElementNamesInParent(ws, "alpha", "beta", architectureRef) || !connectorByElementNamesInParent(ws, "alpha", "cache", architectureRef) || !connectorByElementNamesInParent(ws, "External traffic", "alpha", architectureRef) {
120154
t.Fatalf("missing expected architecture connectors: %+v", ws.Connectors)
121155
}
122156
for _, connector := range ws.Connectors {
@@ -160,8 +194,139 @@ services:
160194
if !connectorByElementNames(ws, "worker", "api") {
161195
t.Fatalf("expected env endpoint connector from worker to api, got %+v", ws.Connectors)
162196
}
163-
if countKind(ws, "function") != 0 {
164-
t.Fatalf("architecture evidence should suppress symbol-level output, got %+v", ws.Elements)
197+
architectureRef := refByElementName(ws, "Architecture")
198+
structuralRef := refByElementName(ws, "Structural")
199+
if architectureRef == "" || structuralRef == "" {
200+
t.Fatalf("missing repository sections: %+v", ws.Elements)
201+
}
202+
for _, name := range []string{"worker", "api"} {
203+
ref := refByElementNameWithParent(ws, name, architectureRef)
204+
if ref == "" {
205+
t.Fatalf("architecture element %q should be under Architecture, ref=%q placements=%+v", name, ref, ws.Elements[ref])
206+
}
207+
}
208+
if ref := refByElementName(ws, "Main"); ref == "" {
209+
t.Fatalf("structural symbol should still be materialized: %+v", ws.Elements)
210+
} else if !hasPlacementParent(ws, ref, refByElementName(ws, "main.go")) {
211+
t.Fatalf("symbol should remain nested under its file view: %+v", ws.Elements[ref].Placements)
212+
}
213+
if ref := refByElementName(ws, "main.go"); ref == "" || !hasPlacementParent(ws, ref, structuralRef) {
214+
t.Fatalf("top-level structural file should be under Structural, ref=%q elements=%+v", ref, ws.Elements)
215+
}
216+
}
217+
218+
func TestAnalyzeCmd_CrossRepositoryArchitectureLinksReuseStructuralElements(t *testing.T) {
219+
dir := t.TempDir()
220+
dataDir := t.TempDir()
221+
cmd.MustInitWorkspace(t, dir)
222+
sourceRepo := filepath.Join(dir, "source-repo")
223+
cmd.InitGitRepo(t, sourceRepo, "modules/cart/main.go", "package main\nfunc ServeCart() {}\n")
224+
runtimeRepo := filepath.Join(dir, "runtime-repo")
225+
cmd.InitGitRepo(t, runtimeRepo, "deploy/cart.yaml", `
226+
apiVersion: apps/v1
227+
kind: Deployment
228+
metadata:
229+
name: cart
230+
spec:
231+
template:
232+
spec:
233+
containers:
234+
- name: app
235+
image: example/cart
236+
env:
237+
- name: PEER
238+
value: "cache:6379"
239+
---
240+
apiVersion: v1
241+
kind: Service
242+
metadata:
243+
name: cache
244+
spec:
245+
ports:
246+
- port: 6379
247+
`)
248+
249+
stdout, stderr, err := cmd.RunCmd(t, dir, "analyze", sourceRepo, "--data-dir", dataDir, "--embedding-provider", "none")
250+
if err != nil {
251+
t.Fatalf("analyze source: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
252+
}
253+
stdout, stderr, err = cmd.RunCmd(t, dir, "analyze", runtimeRepo, "--data-dir", dataDir, "--embedding-provider", "none")
254+
if err != nil {
255+
t.Fatalf("analyze runtime: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
256+
}
257+
ws, err := workspace.Load(dir)
258+
if err != nil {
259+
t.Fatal(err)
260+
}
261+
runtimeRef := refByElementName(ws, "runtime-repo")
262+
architectureRef := refByElementNameWithParent(ws, "Architecture", runtimeRef)
263+
cartArchRef := refByElementNameWithParent(ws, "cart", architectureRef)
264+
cartFolderRef := refByKindAndFilePath(ws, "folder", "modules/cart")
265+
if runtimeRef == "" || architectureRef == "" || cartArchRef == "" || cartFolderRef == "" {
266+
t.Fatalf("missing cross-repo test elements: runtime=%q architecture=%q cartArch=%q cartFolder=%q elements=%+v", runtimeRef, architectureRef, cartArchRef, cartFolderRef, ws.Elements)
267+
}
268+
if !ws.Elements[cartArchRef].HasView || !hasPlacementParent(ws, cartFolderRef, cartArchRef) {
269+
t.Fatalf("runtime architecture component should deep-link to source repo structural folder: arch=%+v folder=%+v", ws.Elements[cartArchRef], ws.Elements[cartFolderRef])
270+
}
271+
if placementCount(ws, cartFolderRef) < 2 {
272+
t.Fatalf("source structural folder should remain in its original structural view and be reused in runtime deep-dive view: %+v", ws.Elements[cartFolderRef])
273+
}
274+
}
275+
276+
func TestAnalyzeCmd_PrunesDisconnectedArchitectureComponents(t *testing.T) {
277+
dir := t.TempDir()
278+
dataDir := t.TempDir()
279+
cmd.MustInitWorkspace(t, dir)
280+
repoDir := filepath.Join(dir, "runtime-app")
281+
cmd.InitGitRepo(t, repoDir, "main.go", "package main\nfunc Main() {}\n")
282+
writeAnalyzeTestFile(t, repoDir, "deploy/topology.yaml", `
283+
apiVersion: apps/v1
284+
kind: Deployment
285+
metadata:
286+
name: connected
287+
spec:
288+
template:
289+
spec:
290+
containers:
291+
- name: app
292+
image: example/connected:latest
293+
env:
294+
- name: PEER
295+
value: "target:9090"
296+
---
297+
apiVersion: v1
298+
kind: Service
299+
metadata:
300+
name: target
301+
spec:
302+
ports:
303+
- port: 9090
304+
---
305+
apiVersion: v1
306+
kind: Service
307+
metadata:
308+
name: isolated
309+
spec:
310+
ports:
311+
- port: 9999
312+
`)
313+
314+
stdout, stderr, err := cmd.RunCmd(t, dir, "analyze", repoDir, "--data-dir", dataDir, "--embedding-provider", "none")
315+
if err != nil {
316+
t.Fatalf("analyze: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
317+
}
318+
ws, err := workspace.Load(dir)
319+
if err != nil {
320+
t.Fatal(err)
321+
}
322+
architectureRef := refByElementName(ws, "Architecture")
323+
for ref, element := range ws.Elements {
324+
if element.Name == "isolated" && hasPlacementParent(ws, ref, architectureRef) {
325+
t.Fatalf("disconnected architecture element should be pruned from Architecture: %+v", ws.Elements)
326+
}
327+
}
328+
if !connectorByElementNamesInParent(ws, "connected", "target", architectureRef) {
329+
t.Fatalf("expected connected architecture edge, got %+v", ws.Connectors)
165330
}
166331
}
167332

@@ -276,6 +441,68 @@ func refByElementName(ws *workspace.Workspace, name string) string {
276441
return ""
277442
}
278443

444+
func refByElementNameWithParent(ws *workspace.Workspace, name, parentRef string) string {
445+
for ref, element := range ws.Elements {
446+
if element.Name == name && hasPlacementParent(ws, ref, parentRef) {
447+
return ref
448+
}
449+
}
450+
return ""
451+
}
452+
453+
func refByKind(ws *workspace.Workspace, kind string) string {
454+
for ref, element := range ws.Elements {
455+
if element.Kind == kind {
456+
return ref
457+
}
458+
}
459+
return ""
460+
}
461+
462+
func refByKindAndFilePath(ws *workspace.Workspace, kind, filePath string) string {
463+
for ref, element := range ws.Elements {
464+
if element.Kind == kind && element.FilePath == filePath {
465+
return ref
466+
}
467+
}
468+
return ""
469+
}
470+
471+
func hasPlacementParent(ws *workspace.Workspace, ref, parentRef string) bool {
472+
element := ws.Elements[ref]
473+
if element == nil {
474+
return false
475+
}
476+
for _, placement := range element.Placements {
477+
if placement.ParentRef == parentRef {
478+
return true
479+
}
480+
}
481+
return false
482+
}
483+
484+
func placementCount(ws *workspace.Workspace, ref string) int {
485+
element := ws.Elements[ref]
486+
if element == nil {
487+
return 0
488+
}
489+
return len(element.Placements)
490+
}
491+
492+
func connectorByElementNamesInParent(ws *workspace.Workspace, sourceName, targetName, parentRef string) bool {
493+
sourceRef := refByElementNameWithParent(ws, sourceName, parentRef)
494+
targetRef := refByElementNameWithParent(ws, targetName, parentRef)
495+
if sourceRef == "" || targetRef == "" {
496+
return false
497+
}
498+
for _, connector := range ws.Connectors {
499+
if connector.Source == sourceRef && connector.Target == targetRef {
500+
return true
501+
}
502+
}
503+
return false
504+
}
505+
279506
func connectorByElementNames(ws *workspace.Workspace, sourceName, targetName string) bool {
280507
sourceRef := refByElementName(ws, sourceName)
281508
targetRef := refByElementName(ws, targetName)

cmd/watch/watch.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ func newRepresentCmd() *cobra.Command {
468468
if err != nil {
469469
return err
470470
}
471-
result, err := watch.NewRepresenter(watchStore).Represent(cmd.Context(), scanResult.RepositoryID, watch.RepresentRequest{Embedding: embeddingCfg, Thresholds: watchSettings.Thresholds, Progress: progress})
471+
result, err := watch.NewRepresenter(watchStore).Represent(cmd.Context(), scanResult.RepositoryID, watch.RepresentRequest{Embedding: embeddingCfg, Thresholds: watchSettings.Thresholds, Visibility: watchSettings.Visibility, Progress: progress})
472472
if err != nil {
473473
return err
474474
}
@@ -699,6 +699,25 @@ func resolveWatchSettings(cfg *workspace.Config, languages []string, watcherMode
699699
MaxOutgoingPerElement: cfg.Watch.Thresholds.MaxOutgoingPerElement,
700700
MaxExpandedConnectorsPerGroup: cfg.Watch.Thresholds.MaxExpandedConnectorsPerGroup,
701701
}
702+
settings.Visibility = watch.VisibilityConfig{
703+
CoreThresholdEnabled: cfg.Watch.Visibility.CoreThresholdEnabled,
704+
CoreThreshold: cfg.Watch.Visibility.CoreThreshold,
705+
TierMultiplier: cfg.Watch.Visibility.TierMultiplier,
706+
MaxExpansionMultiplier: cfg.Watch.Visibility.MaxExpansionMultiplier,
707+
CoreThresholdSet: true,
708+
WeightsSet: true,
709+
Weights: watch.VisibilityWeights{
710+
Changed: cfg.Watch.Visibility.Weights.Changed,
711+
Selected: cfg.Watch.Visibility.Weights.Selected,
712+
UserShow: cfg.Watch.Visibility.Weights.UserShow,
713+
UserHide: cfg.Watch.Visibility.Weights.UserHide,
714+
HighSignalFact: cfg.Watch.Visibility.Weights.HighSignalFact,
715+
RelationshipProximity: cfg.Watch.Visibility.Weights.RelationshipProximity,
716+
DependencyFact: cfg.Watch.Visibility.Weights.DependencyFact,
717+
UtilityNoise: cfg.Watch.Visibility.Weights.UtilityNoise,
718+
HighDegreeNoise: cfg.Watch.Visibility.Weights.HighDegreeNoise,
719+
},
720+
}
702721
}
703722
if len(languages) > 0 {
704723
settings.Languages = languages

0 commit comments

Comments
 (0)