Skip to content

Commit

Permalink
Get ancestor for displaying dependency tree in relationships (#927)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarun06 authored Feb 1, 2024
1 parent 663352d commit f48852b
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;

using System.Collections.Generic;
using Newtonsoft.Json;
Expand All @@ -21,6 +21,8 @@ public class ScannedComponent

public IEnumerable<TypedComponent.TypedComponent> TopLevelReferrers { get; set; }

public IEnumerable<TypedComponent.TypedComponent> AncestralReferrers { get; set; }

public IEnumerable<int> ContainerDetailIds { get; set; }

public IDictionary<int, IEnumerable<int>> ContainerLayerIds { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public DetectedComponent(TypedComponent.TypedComponent component, IComponentDete
/// <summary> Gets or sets the dependency roots for this component. </summary>
public HashSet<TypedComponent.TypedComponent> DependencyRoots { get; set; }

/// <summary> Gets or sets the ancester dependency for this component. </summary>
public HashSet<TypedComponent.TypedComponent> AncestralDependencyRoots { get; set; }

/// <summary>Gets or sets the flag to mark the component as a development dependency or not.
/// This is used at build or development time not a distributed dependency.</summary>
public bool? DevelopmentDependency { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ private IEnumerable<DetectedComponent> GatherSetOfDetectedComponentsUnmerged(IEn

// Calculate roots of the component
this.AddRootsToDetectedComponent(component, dependencyGraph, componentRecorder);

// Calculate Ancestors of the component
this.AddAncestorsToDetectedComponent(component, dependencyGraph, componentRecorder);
component.DevelopmentDependency = this.MergeDevDependency(component.DevelopmentDependency, dependencyGraph.IsDevelopmentDependency(component.Component.Id));
component.DependencyScope = DependencyScopeComparer.GetMergedDependencyScope(component.DependencyScope, dependencyGraph.GetDependencyScope(component.Component.Id));
component.DetectedBy = detector;
Expand Down Expand Up @@ -201,6 +204,23 @@ private void AddRootsToDetectedComponent(DetectedComponent detectedComponent, ID
}
}

private void AddAncestorsToDetectedComponent(DetectedComponent detectedComponent, IDependencyGraph dependencyGraph, IComponentRecorder componentRecorder)
{
detectedComponent.AncestralDependencyRoots ??= new HashSet<TypedComponent>(new ComponentComparer());

if (dependencyGraph == null)
{
return;
}

var roots = dependencyGraph.GetAncestors(detectedComponent.Component.Id);

foreach (var rootId in roots)
{
detectedComponent.AncestralDependencyRoots.Add(componentRecorder.GetComponent(rootId));
}
}

private HashSet<string> MakeFilePathsRelative(ILogger logger, DirectoryInfo rootDirectory, HashSet<string> filePaths)
{
if (rootDirectory == null)
Expand Down Expand Up @@ -248,6 +268,7 @@ private ScannedComponent ConvertToContract(DetectedComponent component)
LocationsFoundAt = component.FilePaths,
Component = component.Component,
TopLevelReferrers = component.DependencyRoots,
AncestralReferrers = component.AncestralDependencyRoots,
ContainerDetailIds = component.ContainerDetailIds,
ContainerLayerIds = component.ContainerLayerIds,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,38 @@ public async Task VerifyTranslation_RootsFromMultipleLocationsAreAgregatedAsync(
}

[TestMethod]
public async Task VerifyTranslation_AncesterFromMultipleLocationsAreAgregatedAsync()
{
var componentRecorder = new ComponentRecorder();
var npmDetector = new Mock<IComponentDetector>();
var settings = new ScanSettings
{
SourceDirectory = this.sourceDirectory,
};

var singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("location1");
var detectedComponent1 = new DetectedComponent(new NpmComponent("test", "1.0.0"), detector: npmDetector.Object);
var detectedComponent2 = new DetectedComponent(new NpmComponent("test", "2.0.0"), detector: npmDetector.Object);

singleFileComponentRecorder.RegisterUsage(detectedComponent1, isExplicitReferencedDependency: true);
singleFileComponentRecorder.RegisterUsage(detectedComponent2, parentComponentId: detectedComponent1.Component.Id);

singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("location2");
var detectedComponent2NewLocation = new DetectedComponent(new NpmComponent("test", "2.0.0"), detector: npmDetector.Object);
singleFileComponentRecorder.RegisterUsage(detectedComponent2NewLocation, isExplicitReferencedDependency: true);

var results = await this.SetupRecorderBasedScanningAsync(settings, new List<ComponentRecorder> { componentRecorder });

var detectedComponents = results.ComponentsFound;

var storedComponent1 = detectedComponents.First(dc => dc.Component.Id == detectedComponent1.Component.Id);
storedComponent1.AncestralReferrers.Should().BeEmpty("If a component is a root, then no root of itself");

var storedComponent2 = detectedComponents.First(dc => dc.Component.Id == detectedComponent2.Component.Id);
storedComponent2.AncestralReferrers.Should().ContainSingle("There 1 roots, the component is root of other component");
storedComponent2.AncestralReferrers.Should().Contain(detectedComponent1.Component);
}

public async Task VerifyTranslation_ComponentsAreReturnedWithRootsAsync()
{
var componentRecorder = new ComponentRecorder();
Expand Down Expand Up @@ -440,6 +472,35 @@ public async Task VerifyTranslation_ComponentsAreReturnedWithRootsAsync()
storedComponent2.TopLevelReferrers.Should().Contain(detectedComponent1.Component);
}

[TestMethod]
public async Task VerifyTranslation_ComponentsAreReturnedWithAncesterAsync()
{
var componentRecorder = new ComponentRecorder();
var npmDetector = new Mock<IComponentDetector>();
var settings = new ScanSettings
{
SourceDirectory = this.sourceDirectory,
};

var singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("location");
var detectedComponent1 = new DetectedComponent(new NpmComponent("test", "1.0.0"), detector: npmDetector.Object);
var detectedComponent2 = new DetectedComponent(new NpmComponent("test", "2.0.0"), detector: npmDetector.Object);

singleFileComponentRecorder.RegisterUsage(detectedComponent1, isExplicitReferencedDependency: true);
singleFileComponentRecorder.RegisterUsage(detectedComponent2, parentComponentId: detectedComponent1.Component.Id);

var results = await this.SetupRecorderBasedScanningAsync(settings, new List<ComponentRecorder> { componentRecorder });

var detectedComponents = results.ComponentsFound;

var storedComponent1 = detectedComponents.First(dc => dc.Component.Id == detectedComponent1.Component.Id);
storedComponent1.AncestralReferrers.Should().BeEmpty("If a component is a root, then no root of itself");

var storedComponent2 = detectedComponents.First(dc => dc.Component.Id == detectedComponent2.Component.Id);
storedComponent2.AncestralReferrers.Should().ContainSingle();
storedComponent2.AncestralReferrers.Should().Contain(detectedComponent1.Component);
}

[TestMethod]
public async Task VerifyTranslation_DevDependenciesAreMergedWhenSameComponentInDifferentFilesAsync()
{
Expand Down

0 comments on commit f48852b

Please sign in to comment.