Skip to content

Commit f48852b

Browse files
authored
Get ancestor for displaying dependency tree in relationships (#927)
1 parent 663352d commit f48852b

File tree

4 files changed

+88
-1
lines changed

4 files changed

+88
-1
lines changed

src/Microsoft.ComponentDetection.Contracts/BcdeModels/ScannedComponent.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
1+
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
22

33
using System.Collections.Generic;
44
using Newtonsoft.Json;
@@ -21,6 +21,8 @@ public class ScannedComponent
2121

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

24+
public IEnumerable<TypedComponent.TypedComponent> AncestralReferrers { get; set; }
25+
2426
public IEnumerable<int> ContainerDetailIds { get; set; }
2527

2628
public IDictionary<int, IEnumerable<int>> ContainerLayerIds { get; set; }

src/Microsoft.ComponentDetection.Contracts/DetectedComponent.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public DetectedComponent(TypedComponent.TypedComponent component, IComponentDete
4646
/// <summary> Gets or sets the dependency roots for this component. </summary>
4747
public HashSet<TypedComponent.TypedComponent> DependencyRoots { get; set; }
4848

49+
/// <summary> Gets or sets the ancester dependency for this component. </summary>
50+
public HashSet<TypedComponent.TypedComponent> AncestralDependencyRoots { get; set; }
51+
4952
/// <summary>Gets or sets the flag to mark the component as a development dependency or not.
5053
/// This is used at build or development time not a distributed dependency.</summary>
5154
public bool? DevelopmentDependency { get; set; }

src/Microsoft.ComponentDetection.Orchestrator/Services/GraphTranslation/DefaultGraphTranslationService.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ private IEnumerable<DetectedComponent> GatherSetOfDetectedComponentsUnmerged(IEn
8888

8989
// Calculate roots of the component
9090
this.AddRootsToDetectedComponent(component, dependencyGraph, componentRecorder);
91+
92+
// Calculate Ancestors of the component
93+
this.AddAncestorsToDetectedComponent(component, dependencyGraph, componentRecorder);
9194
component.DevelopmentDependency = this.MergeDevDependency(component.DevelopmentDependency, dependencyGraph.IsDevelopmentDependency(component.Component.Id));
9295
component.DependencyScope = DependencyScopeComparer.GetMergedDependencyScope(component.DependencyScope, dependencyGraph.GetDependencyScope(component.Component.Id));
9396
component.DetectedBy = detector;
@@ -201,6 +204,23 @@ private void AddRootsToDetectedComponent(DetectedComponent detectedComponent, ID
201204
}
202205
}
203206

207+
private void AddAncestorsToDetectedComponent(DetectedComponent detectedComponent, IDependencyGraph dependencyGraph, IComponentRecorder componentRecorder)
208+
{
209+
detectedComponent.AncestralDependencyRoots ??= new HashSet<TypedComponent>(new ComponentComparer());
210+
211+
if (dependencyGraph == null)
212+
{
213+
return;
214+
}
215+
216+
var roots = dependencyGraph.GetAncestors(detectedComponent.Component.Id);
217+
218+
foreach (var rootId in roots)
219+
{
220+
detectedComponent.AncestralDependencyRoots.Add(componentRecorder.GetComponent(rootId));
221+
}
222+
}
223+
204224
private HashSet<string> MakeFilePathsRelative(ILogger logger, DirectoryInfo rootDirectory, HashSet<string> filePaths)
205225
{
206226
if (rootDirectory == null)
@@ -248,6 +268,7 @@ private ScannedComponent ConvertToContract(DetectedComponent component)
248268
LocationsFoundAt = component.FilePaths,
249269
Component = component.Component,
250270
TopLevelReferrers = component.DependencyRoots,
271+
AncestralReferrers = component.AncestralDependencyRoots,
251272
ContainerDetailIds = component.ContainerDetailIds,
252273
ContainerLayerIds = component.ContainerLayerIds,
253274
};

test/Microsoft.ComponentDetection.Orchestrator.Tests/Services/BcdeScanExecutionServiceTests.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,38 @@ public async Task VerifyTranslation_RootsFromMultipleLocationsAreAgregatedAsync(
411411
}
412412

413413
[TestMethod]
414+
public async Task VerifyTranslation_AncesterFromMultipleLocationsAreAgregatedAsync()
415+
{
416+
var componentRecorder = new ComponentRecorder();
417+
var npmDetector = new Mock<IComponentDetector>();
418+
var settings = new ScanSettings
419+
{
420+
SourceDirectory = this.sourceDirectory,
421+
};
422+
423+
var singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("location1");
424+
var detectedComponent1 = new DetectedComponent(new NpmComponent("test", "1.0.0"), detector: npmDetector.Object);
425+
var detectedComponent2 = new DetectedComponent(new NpmComponent("test", "2.0.0"), detector: npmDetector.Object);
426+
427+
singleFileComponentRecorder.RegisterUsage(detectedComponent1, isExplicitReferencedDependency: true);
428+
singleFileComponentRecorder.RegisterUsage(detectedComponent2, parentComponentId: detectedComponent1.Component.Id);
429+
430+
singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("location2");
431+
var detectedComponent2NewLocation = new DetectedComponent(new NpmComponent("test", "2.0.0"), detector: npmDetector.Object);
432+
singleFileComponentRecorder.RegisterUsage(detectedComponent2NewLocation, isExplicitReferencedDependency: true);
433+
434+
var results = await this.SetupRecorderBasedScanningAsync(settings, new List<ComponentRecorder> { componentRecorder });
435+
436+
var detectedComponents = results.ComponentsFound;
437+
438+
var storedComponent1 = detectedComponents.First(dc => dc.Component.Id == detectedComponent1.Component.Id);
439+
storedComponent1.AncestralReferrers.Should().BeEmpty("If a component is a root, then no root of itself");
440+
441+
var storedComponent2 = detectedComponents.First(dc => dc.Component.Id == detectedComponent2.Component.Id);
442+
storedComponent2.AncestralReferrers.Should().ContainSingle("There 1 roots, the component is root of other component");
443+
storedComponent2.AncestralReferrers.Should().Contain(detectedComponent1.Component);
444+
}
445+
414446
public async Task VerifyTranslation_ComponentsAreReturnedWithRootsAsync()
415447
{
416448
var componentRecorder = new ComponentRecorder();
@@ -440,6 +472,35 @@ public async Task VerifyTranslation_ComponentsAreReturnedWithRootsAsync()
440472
storedComponent2.TopLevelReferrers.Should().Contain(detectedComponent1.Component);
441473
}
442474

475+
[TestMethod]
476+
public async Task VerifyTranslation_ComponentsAreReturnedWithAncesterAsync()
477+
{
478+
var componentRecorder = new ComponentRecorder();
479+
var npmDetector = new Mock<IComponentDetector>();
480+
var settings = new ScanSettings
481+
{
482+
SourceDirectory = this.sourceDirectory,
483+
};
484+
485+
var singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("location");
486+
var detectedComponent1 = new DetectedComponent(new NpmComponent("test", "1.0.0"), detector: npmDetector.Object);
487+
var detectedComponent2 = new DetectedComponent(new NpmComponent("test", "2.0.0"), detector: npmDetector.Object);
488+
489+
singleFileComponentRecorder.RegisterUsage(detectedComponent1, isExplicitReferencedDependency: true);
490+
singleFileComponentRecorder.RegisterUsage(detectedComponent2, parentComponentId: detectedComponent1.Component.Id);
491+
492+
var results = await this.SetupRecorderBasedScanningAsync(settings, new List<ComponentRecorder> { componentRecorder });
493+
494+
var detectedComponents = results.ComponentsFound;
495+
496+
var storedComponent1 = detectedComponents.First(dc => dc.Component.Id == detectedComponent1.Component.Id);
497+
storedComponent1.AncestralReferrers.Should().BeEmpty("If a component is a root, then no root of itself");
498+
499+
var storedComponent2 = detectedComponents.First(dc => dc.Component.Id == detectedComponent2.Component.Id);
500+
storedComponent2.AncestralReferrers.Should().ContainSingle();
501+
storedComponent2.AncestralReferrers.Should().Contain(detectedComponent1.Component);
502+
}
503+
443504
[TestMethod]
444505
public async Task VerifyTranslation_DevDependenciesAreMergedWhenSameComponentInDifferentFilesAsync()
445506
{

0 commit comments

Comments
 (0)