Skip to content

Commit aab2eba

Browse files
committed
Show Package Owner profile links on Browse Tab
1 parent 0e21635 commit aab2eba

23 files changed

Lines changed: 841 additions & 85 deletions

src/NuGet.Clients/NuGet.PackageManagement.UI/PackageItemLoader.cs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
using Microsoft.VisualStudio.Shell;
1414
using Microsoft.VisualStudio.Threading;
1515
using NuGet.Common;
16+
using NuGet.PackageManagement.UI.ViewModels;
1617
using NuGet.PackageManagement.VisualStudio;
17-
using NuGet.Packaging.Core;
1818
using NuGet.Protocol.Core.Types;
1919
using NuGet.Versioning;
2020
using NuGet.VisualStudio;
@@ -262,17 +262,17 @@ public IEnumerable<PackageItemViewModel> GetCurrent()
262262
return Enumerable.Empty<PackageItemViewModel>();
263263
}
264264

265-
var listItemViewModels = new List<PackageItemViewModel>();
265+
var listItemViewModels = new List<PackageItemViewModel>(capacity: _state.ItemsCount);
266266

267-
foreach (PackageSearchMetadataContextInfo metadata in _state.Results.PackageSearchItems)
267+
foreach (PackageSearchMetadataContextInfo metadataContextInfo in _state.Results.PackageSearchItems)
268268
{
269269
VersionRange allowedVersions = VersionRange.All;
270270
VersionRange versionOverride = null;
271271

272272
// get the allowed version range and pass it to package item view model to choose the latest version based on that
273273
if (_packageReferences != null)
274274
{
275-
IEnumerable<IPackageReferenceContextInfo> matchedPackageReferences = _packageReferences.Where(r => StringComparer.OrdinalIgnoreCase.Equals(r.Identity.Id, metadata.Identity.Id));
275+
IEnumerable<IPackageReferenceContextInfo> matchedPackageReferences = _packageReferences.Where(r => StringComparer.OrdinalIgnoreCase.Equals(r.Identity.Id, metadataContextInfo.Identity.Id));
276276
VersionRange[] allowedVersionsRange = matchedPackageReferences.Select(r => r.AllowedVersions).Where(v => v != null).ToArray();
277277
VersionRange[] versionOverrides = matchedPackageReferences.Select(r => r.VersionOverride).Where(v => v != null).ToArray();
278278

@@ -287,30 +287,34 @@ public IEnumerable<PackageItemViewModel> GetCurrent()
287287
}
288288
}
289289

290-
var packageLevel = metadata.TransitiveOrigins != null ? PackageLevel.Transitive : PackageLevel.TopLevel;
290+
var packageLevel = metadataContextInfo.TransitiveOrigins != null ? PackageLevel.Transitive : PackageLevel.TopLevel;
291291

292292
var transitiveToolTipMessage = string.Empty;
293293
if (packageLevel == PackageLevel.Transitive)
294294
{
295-
transitiveToolTipMessage = string.Format(CultureInfo.CurrentCulture, Resources.PackageVersionWithTransitiveOrigins, metadata.Identity.Version, string.Join(", ", metadata.TransitiveOrigins));
295+
transitiveToolTipMessage = string.Format(CultureInfo.CurrentCulture, Resources.PackageVersionWithTransitiveOrigins, metadataContextInfo.Identity.Version, string.Join(", ", metadataContextInfo.TransitiveOrigins));
296296
}
297297

298+
ImmutableList<KnownOwnerViewModel> knownOwnerViewModels = LoadKnownOwnerViewModels(metadataContextInfo);
299+
298300
var listItem = new PackageItemViewModel(_searchService, _packageVulnerabilityService)
299301
{
300-
Id = metadata.Identity.Id,
301-
Version = metadata.Identity.Version,
302-
IconUrl = metadata.IconUrl,
303-
Author = metadata.Authors,
304-
DownloadCount = metadata.DownloadCount,
305-
Summary = metadata.Summary,
302+
Id = metadataContextInfo.Identity.Id,
303+
Version = metadataContextInfo.Identity.Version,
304+
IconUrl = metadataContextInfo.IconUrl,
305+
Owner = metadataContextInfo.Owners,
306+
KnownOwnerViewModels = knownOwnerViewModels,
307+
Author = metadataContextInfo.Authors,
308+
DownloadCount = metadataContextInfo.DownloadCount,
309+
Summary = metadataContextInfo.Summary,
306310
AllowedVersions = allowedVersions,
307311
VersionOverride = versionOverride,
308-
PrefixReserved = metadata.PrefixReserved && !IsMultiSource,
309-
Recommended = metadata.IsRecommended,
310-
RecommenderVersion = metadata.RecommenderVersion,
311-
Vulnerabilities = metadata.Vulnerabilities,
312+
PrefixReserved = metadataContextInfo.PrefixReserved && !IsMultiSource,
313+
Recommended = metadataContextInfo.IsRecommended,
314+
RecommenderVersion = metadataContextInfo.RecommenderVersion,
315+
Vulnerabilities = metadataContextInfo.Vulnerabilities,
312316
Sources = _packageSources,
313-
PackagePath = metadata.PackagePath,
317+
PackagePath = metadataContextInfo.PackagePath,
314318
PackageFileService = _packageFileService,
315319
IncludePrerelease = _includePrerelease,
316320
PackageLevel = packageLevel,
@@ -323,7 +327,7 @@ public IEnumerable<PackageItemViewModel> GetCurrent()
323327
}
324328
else
325329
{
326-
listItem.UpdateTransitivePackageStatus(metadata.Identity.Version);
330+
listItem.UpdateTransitivePackageStatus(metadataContextInfo.Identity.Version);
327331
}
328332

329333
listItemViewModels.Add(listItem);
@@ -332,18 +336,15 @@ public IEnumerable<PackageItemViewModel> GetCurrent()
332336
return listItemViewModels.ToArray();
333337
}
334338

335-
private async Task<IReadOnlyCollection<VersionInfoContextInfo>> GetVersionInfoAsync(PackageIdentity identity)
339+
private static ImmutableList<KnownOwnerViewModel> LoadKnownOwnerViewModels(PackageSearchMetadataContextInfo metadataContextInfo)
336340
{
337-
Assumes.NotNull(identity);
338-
339-
return await _searchService.GetPackageVersionsAsync(identity, _packageSources, _includePrerelease, CancellationToken.None);
340-
}
341-
342-
private async Task<(PackageSearchMetadataContextInfo, PackageDeprecationMetadataContextInfo)> GetDetailedPackageSearchMetadataContextInfoAsync(PackageIdentity identity)
343-
{
344-
Assumes.NotNull(identity);
341+
ImmutableList<KnownOwnerViewModel> knownOwnerViewModels = null;
342+
if (metadataContextInfo.KnownOwners != null)
343+
{
344+
knownOwnerViewModels = metadataContextInfo.KnownOwners.Select(knownOwner => new KnownOwnerViewModel(knownOwner)).ToImmutableList();
345+
}
345346

346-
return await _searchService.GetPackageMetadataAsync(identity, _packageSources, _includePrerelease, CancellationToken.None);
347+
return knownOwnerViewModels;
347348
}
348349

349350
public void Dispose()

src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,4 +1042,10 @@ Please see https://aka.ms/troubleshoot_nuget_cache for more help.</value>
10421042
<value>The package `{0}` is available in the Global packages folder, but the source it came from `{1}` is not one of the configured sources.</value>
10431043
<comment>{0} is the package ID. {1} is the URI of the package source found in the Global packages folder.</comment>
10441044
</data>
1045+
<data name="Text_ByOwner" xml:space="preserve">
1046+
<value>by {0}</value>
1047+
</data>
1048+
<data name="Text_By" xml:space="preserve">
1049+
<value>by</value>
1050+
</data>
10451051
</root>

src/NuGet.Clients/NuGet.PackageManagement.UI/Resources/Resources.xaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
x:Key="EmptyEnumerableToVisibilityConverter" />
2424
<nuget:EnumerableToVisibilityConverter
2525
x:Key="EnumerableToVisibilityConverter" />
26+
<nuget:LastItemToVisibilityConverter
27+
x:Key="LastItemToVisibilityConverter" />
2628
<nuget:InverseBooleanConverter
2729
x:Key="InverseBooleanConverter" />
2830
<nuget:BooleanToGridRowHeightConverter
@@ -684,6 +686,29 @@
684686
</Style.Triggers>
685687
</Style>
686688

689+
<Style x:Key="HyperlinkSelectorAware" TargetType="{x:Type Hyperlink}" BasedOn="{StaticResource {x:Static vs:VsResourceKeys.ThemedDialogHyperlinkStyleKey}}">
690+
<Style.Triggers>
691+
<MultiDataTrigger>
692+
<MultiDataTrigger.Conditions>
693+
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" Value="True" />
694+
<Condition Binding="{Binding (Selector.IsSelectionActive), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" Value="False" />
695+
</MultiDataTrigger.Conditions>
696+
<Setter
697+
Property="Foreground"
698+
Value="{DynamicResource {x:Static nuget:Brushes.ContentInactiveSelectedTextBrushKey}}" />
699+
</MultiDataTrigger>
700+
<MultiDataTrigger>
701+
<MultiDataTrigger.Conditions>
702+
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" Value="True" />
703+
<Condition Binding="{Binding (Selector.IsSelectionActive), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" Value="True" />
704+
</MultiDataTrigger.Conditions>
705+
<Setter
706+
Property="Foreground"
707+
Value="{DynamicResource {x:Static nuget:Brushes.ContentSelectedTextBrushKey}}" />
708+
</MultiDataTrigger>
709+
</Style.Triggers>
710+
</Style>
711+
687712
<Style
688713
x:Key="PackageIconImageStyle"
689714
TargetType="{x:Type Image}">
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Globalization;
7+
using System.Windows;
8+
using System.Windows.Data;
9+
10+
namespace NuGet.PackageManagement.UI
11+
{
12+
internal class LastItemToVisibilityConverter : IMultiValueConverter
13+
{
14+
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
15+
{
16+
if (values != null && values.Length == 2
17+
&& values[0] is int currentIndex
18+
&& values[1] is int length)
19+
{
20+
if (currentIndex < 0 || length < 1 || currentIndex >= length)
21+
{
22+
return DependencyProperty.UnsetValue;
23+
}
24+
25+
int lastIndex = length - 1;
26+
return Equals(currentIndex, lastIndex) ? Visibility.Collapsed : Visibility.Visible;
27+
}
28+
29+
return DependencyProperty.UnsetValue;
30+
}
31+
32+
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
33+
{
34+
Debug.Fail("Not Implemented");
35+
return null;
36+
}
37+
}
38+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using NuGet.VisualStudio.Internal.Contracts;
6+
7+
namespace NuGet.PackageManagement.UI.ViewModels
8+
{
9+
public class KnownOwnerViewModel
10+
{
11+
private string _name;
12+
private Uri _link;
13+
14+
public KnownOwnerViewModel(KnownOwner knownOwner)
15+
{
16+
_name = knownOwner.Name;
17+
_link = knownOwner.Link;
18+
}
19+
20+
public string Name => _name;
21+
22+
public Uri Link => _link;
23+
}
24+
}

src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageItemViewModel.cs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.ComponentModel;
78
using System.Globalization;
89
using System.IO;
@@ -15,6 +16,7 @@
1516
using System.Windows.Media.Imaging;
1617
using Microsoft;
1718
using Microsoft.VisualStudio.Threading;
19+
using NuGet.PackageManagement.UI.ViewModels;
1820
using NuGet.PackageManagement.VisualStudio;
1921
using NuGet.Packaging.Core;
2022
using NuGet.Versioning;
@@ -64,6 +66,10 @@ public PackageItemViewModel(INuGetSearchService searchService, IPackageVulnerabi
6466

6567
public bool IncludePrerelease { get; set; }
6668

69+
public ImmutableList<KnownOwnerViewModel> KnownOwnerViewModels { get; internal set; }
70+
71+
public string Owner { get; internal set; }
72+
6773
private string _author;
6874
public string Author
6975
{
@@ -79,11 +85,54 @@ public string Author
7985
}
8086
}
8187

88+
/// <summary>
89+
/// When a collection of <see cref="KnownOwnerViewModels"/> is available, this property returns the <see cref="PackageSearchMetadataContextInfo.Owners"/>
90+
/// string which contains the package owner name(s).
91+
/// If the collection exists but is empty, it's treated as there being no assigned owner for this package by returning an empty string.
92+
/// Otherwise, when there's no collection or no Owners string, it returns null.
93+
/// </summary>
94+
private string ByOwner
95+
{
96+
get
97+
{
98+
// Owners is only used when we have Known Owners.
99+
if (KnownOwnerViewModels == null)
100+
{
101+
return null;
102+
}
103+
104+
// Empty Known Owners is treated as there being no assigned owner for this package.
105+
if (KnownOwnerViewModels.IsEmpty)
106+
{
107+
return string.Empty;
108+
}
109+
110+
// Having Known Owners but with an empty Owners string is treated as there being no assigned owner for this package.
111+
if (string.IsNullOrWhiteSpace(Owner))
112+
{
113+
return string.Empty;
114+
}
115+
116+
return string.Format(CultureInfo.CurrentCulture, Resx.Text_ByOwner, Owner);
117+
}
118+
}
119+
82120
public string ByAuthor
83121
{
84122
get
85123
{
86-
return _author != null ? string.Format(CultureInfo.CurrentCulture, Resx.Text_ByAuthor, _author) : null;
124+
return !string.IsNullOrWhiteSpace(_author) ? string.Format(CultureInfo.CurrentCulture, Resx.Text_ByAuthor, _author) : null;
125+
}
126+
}
127+
128+
/// <summary>
129+
/// Fallback to <see cref="ByAuthor"/> only when <see cref="ByOwner"> is null.
130+
/// </summary>
131+
public string ByOwnerOrAuthor
132+
{
133+
get
134+
{
135+
return ByOwner ?? ByAuthor;
87136
}
88137
}
89138

0 commit comments

Comments
 (0)