Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GetPublishedAtAsync to BaseProjectManager #334

Merged
merged 7 commits into from
Apr 26, 2022
39 changes: 38 additions & 1 deletion src/Shared/PackageManagers/NPMProjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,36 @@ public override async Task<IEnumerable<string>> EnumerateVersionsAsync(PackageUR
}
}

/// <summary>
/// Gets the <see cref="DateTime"/> a package version was published at.
/// </summary>
/// <param name="purl">Package URL specifying the package. Version is mandatory.</param>
/// <param name="useCache">If the cache should be used when looking for the published time.</param>
/// <returns>The <see cref="DateTime"/> when this version was published, or null if not found.</returns>
public async Task<DateTime?> GetPublishedAtAsync(PackageURL purl, bool useCache = true)
{
string? content = await GetMetadataAsync(purl, useCache);
if (string.IsNullOrEmpty(content)) { return null; }

// convert NPM package data to normalized form
jpinz marked this conversation as resolved.
Show resolved Hide resolved
JsonDocument contentJSON = JsonDocument.Parse(content);
JsonElement root = contentJSON.RootElement;

if (!root.TryGetProperty("time", out JsonElement time))
{
return null;
}

string? publishedTime = OssUtilities.GetJSONPropertyStringIfExists(time, purl.Version);
if (publishedTime == null)
{
return null;
}

return DateTime.Parse(publishedTime);

}

/// <summary>
/// Gets the latest version of the package
/// </summary>
Expand Down Expand Up @@ -239,11 +269,18 @@ public override Uri GetPackageAbsoluteUri(PackageURL purl)
metadata.PackageVersion = latestVersion is null ? purl.Version : latestVersion?.ToString();
}

// if we found any version at all, get the deets
// if we found any version at all, get the information
if (metadata.PackageVersion != null)
{
Version versionToGet = new(metadata.PackageVersion);
JsonElement? versionElement = GetVersionElement(contentJSON, versionToGet);

if (root.TryGetProperty("time", out JsonElement time))
{
string? uploadTime = OssUtilities.GetJSONPropertyStringIfExists(time, metadata.PackageVersion);
metadata.UploadTime = uploadTime;
}

if (versionElement != null)
{
// redo the generic values to version specific values
Expand Down
15 changes: 15 additions & 0 deletions src/Shared/PackageManagers/NuGetProjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ private async Task<string> GetRegistrationEndpointAsync()
return RegistrationEndpoint;
}

/// <summary>
/// Gets the <see cref="DateTime"/> a package version was published at.
/// </summary>
/// <param name="purl">Package URL specifying the package. Version is mandatory.</param>
/// <param name="useCache">If the cache should be used when looking for the published time.</param>
/// <returns>The <see cref="DateTime"/> when this version was published, or null if not found.</returns>
public async Task<DateTime?> GetPublishedAtAsync(PackageURL purl, bool useCache = true)
{
Check.NotNull(nameof(purl.Version), purl.Version);
NuGetPackageVersionMetadata? packageVersionMetadata =
await Actions.GetMetadataAsync(purl, useCache: useCache);

return packageVersionMetadata?.Published?.DateTime;
}

/// <inheritdoc />
public override async Task<string?> GetMetadataAsync(PackageURL purl, bool useCache = true)
{
Expand Down
37 changes: 24 additions & 13 deletions src/Shared/PackageManagers/PyPIProjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,26 @@ public override async Task<IEnumerable<string>> EnumerateVersionsAsync(PackageUR
}
}

/// <summary>
/// Gets the <see cref="DateTime"/> a package version was published at.
/// </summary>
/// <param name="purl">Package URL specifying the package. Version is mandatory.</param>
/// <param name="useCache">If the cache should be used when looking for the published time.</param>
/// <returns>The <see cref="DateTime"/> when this version was published, or null if not found.</returns>
public async Task<DateTime?> GetPublishedAtAsync(PackageURL purl, bool useCache = true)
{
Check.NotNull(nameof(purl.Version), purl.Version);
PackageMetadata? metadata = await this.GetPackageMetadataAsync(purl, useCache);

string? uploadTime = metadata?.UploadTime;
if (uploadTime == null)
{
return null;
}

return DateTime.Parse(uploadTime);
}

public override async Task<string?> GetMetadataAsync(PackageURL purl, bool useCache = true)
{
try
Expand Down Expand Up @@ -218,6 +238,8 @@ public override async Task<IEnumerable<string>> EnumerateVersionsAsync(PackageUR
metadata.Name = OssUtilities.GetJSONPropertyStringIfExists(infoElement, "name");
metadata.Description = OssUtilities.GetJSONPropertyStringIfExists(infoElement, "summary"); // Summary is the short description. Description is usually the readme.

metadata.LatestPackageVersion = OssUtilities.GetJSONPropertyStringIfExists(infoElement, "version"); // Ran in the root, always points to latest version.

metadata.PackageManagerUri = ENV_PYPI_ENDPOINT;
metadata.PackageUri = OssUtilities.GetJSONPropertyStringIfExists(infoElement, "package_url");
metadata.Keywords = OssUtilities.ConvertJSONToList(OssUtilities.GetJSONPropertyIfExists(infoElement, "keywords"));
Expand Down Expand Up @@ -266,19 +288,8 @@ public override async Task<IEnumerable<string>> EnumerateVersionsAsync(PackageUR
});
}

// get the version
List<Version> versions = GetVersions(contentJSON);
Version? latestVersion = GetLatestVersion(versions);

if (purl.Version != null)
{
// find the version object from the collection
metadata.PackageVersion = purl.Version;
}
else
{
metadata.PackageVersion = latestVersion is null ? purl.Version : latestVersion?.ToString();
}
// get the version, either use the provided one, or if null then use the LatestPackageVersion.
metadata.PackageVersion = purl.Version ?? metadata.LatestPackageVersion;

// if we found any version at all, get the information.
if (metadata.PackageVersion is not null)
Expand Down
23 changes: 23 additions & 0 deletions src/oss-tests/ProjectManagerTests/NPMProjectManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.CST.OpenSource.Tests.ProjectManagerTests
using PackageManagers;
using PackageUrl;
using RichardSzalay.MockHttp;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -86,6 +87,28 @@ public async Task EnumerateVersionsSucceeds(string purlString, int count, string
Assert.AreEqual(latestVersion, versions.First());
}

[DataTestMethod]
[DataRow("pkg:npm/lodash@4.17.15", "2019-07-19T02:28:46.584Z")]
[DataRow("pkg:npm/%40angular/core@13.2.5", "2022-03-02T18:25:31.169Z")]
[DataRow("pkg:npm/ds-modal@0.0.2", "2018-08-09T07:24:06.206Z")]
[DataRow("pkg:npm/monorepolint@0.4.0", "2019-08-07T16:20:53.525Z")]
[DataRow("pkg:npm/rly-cli@0.0.2", "2022-03-08T17:26:27.219Z")]
[DataRow("pkg:npm/example@0.0.0")] // No time property in the json.
public async Task GetPublishedAtSucceeds(string purlString, string? expectedTime = null)
{
PackageURL purl = new(purlString);
DateTime? time = await _projectManager.GetPublishedAtAsync(purl, useCache: false);

if (expectedTime == null)
{
Assert.IsNull(time);
}
else
{
Assert.AreEqual(DateTime.Parse(expectedTime), time);
}
}

[DataTestMethod]
[DataRow("pkg:npm/lodash@4.17.15", "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz")]
[DataRow("pkg:npm/%40angular/core@13.2.5", "https://registry.npmjs.org/%40angular/core/-/core-13.2.5.tgz")]
Expand Down
18 changes: 18 additions & 0 deletions src/oss-tests/ProjectManagerTests/NuGetProjectManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Microsoft.CST.OpenSource.Tests.ProjectManagerTests
using PackageManagers;
using PackageUrl;
using RichardSzalay.MockHttp;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -122,6 +123,23 @@ public async Task EnumerateVersionsSucceeds(
Assert.AreEqual(latestVersion, versions.First());
}

[DataTestMethod]
[DataRow("pkg:nuget/razorengine@4.2.3-beta1", "2015-10-06T17:53:46.37+00:00")]
[DataRow("pkg:nuget/razorengine@4.5.1-alpha001", "2017-09-02T05:17:55.973-04:00")]
public async Task GetPublishedAtSucceeds(string purlString, string? expectedTime = null)
{
PackageURL purl = new(purlString);
DateTime? time = await _projectManager.GetPublishedAtAsync(purl, useCache: false);

if (expectedTime == null)
{
Assert.IsNull(time);
}
else
{
Assert.AreEqual(DateTime.Parse(expectedTime), time);
}
}

[DataTestMethod]
[DataRow("pkg:nuget/newtonsoft.json@13.0.1", "https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.1/newtonsoft.json")]
Expand Down
20 changes: 19 additions & 1 deletion src/oss-tests/ProjectManagerTests/PyPIProjectManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public PyPIProjectManagerTests()
_projectManager = new PyPIProjectManager(".", new NoOpPackageActions(), _httpFactory);
}

[Ignore(message: "Ignored until https://github.com/microsoft/OSSGadget/issues/328 is addressed.")]
[DataTestMethod]
[DataRow("pkg:pypi/pandas@1.4.2", "Powerful data structures for data analysis, time series, and statistics")]
[DataRow("pkg:pypi/plotly@5.7.0", "An open-source, interactive data visualization library for Python")]
Expand All @@ -64,6 +63,25 @@ public async Task MetadataSucceeds(string purlString, string? description = null
Assert.AreEqual(description, metadata.Description);
}

[DataTestMethod]
[DataRow("pkg:pypi/pandas@1.4.2", "2022-04-02T10:37:04")]
[DataRow("pkg:pypi/plotly@5.7.0", "2022-04-05T16:26:12")]
[DataRow("pkg:pypi/requests@2.27.1", "2022-01-05T15:40:51")]
public async Task GetPublishedAtSucceeds(string purlString, string? expectedTime = null)
{
PackageURL purl = new(purlString);
DateTime? time = await _projectManager.GetPublishedAtAsync(purl, useCache: false);

if (expectedTime == null)
{
Assert.IsNull(time);
}
else
{
Assert.AreEqual(DateTime.Parse(expectedTime), time);
}
}

[DataTestMethod]
[DataRow("pkg:pypi/pandas@1.4.2", 86, "1.4.2")]
[DataRow("pkg:pypi/plotly@3.7.1", 276, "5.7.0")]
Expand Down