diff --git a/.editorconfig b/.editorconfig
index 984a626..557eb2e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -47,7 +47,7 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on separate lines
-csharp_preserve_single_line_blocks = false
+csharp_preserve_single_line_blocks = true
#Style - Code block preferences
diff --git a/Directory.Build.props b/Directory.Build.props
index b4a1e11..e293c53 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -32,7 +32,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index e5c97b0..3af0ea2 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -20,7 +20,7 @@ parameters:
- release
variables:
- MSIXVersion: '0.1100'
+ MSIXVersion: '0.1200'
solution: '**/GitHubExtension.sln'
appxPackageDir: 'AppxPackages'
testOutputArtifactDir: 'TestResults'
diff --git a/build/scripts/CreateBuildInfo.ps1 b/build/scripts/CreateBuildInfo.ps1
index 17a84fc..53b8a5e 100644
--- a/build/scripts/CreateBuildInfo.ps1
+++ b/build/scripts/CreateBuildInfo.ps1
@@ -5,7 +5,7 @@ Param(
)
$Major = "0"
-$Minor = "11"
+$Minor = "12"
$Patch = "99" # default to 99 for local builds
$versionSplit = $Version.Split(".");
diff --git a/codeAnalysis/GlobalSuppressions.cs b/codeAnalysis/GlobalSuppressions.cs
index 6adc7ff..3edd0bd 100644
--- a/codeAnalysis/GlobalSuppressions.cs
+++ b/codeAnalysis/GlobalSuppressions.cs
@@ -7,9 +7,6 @@
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
-[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009:ClosingParenthesisMustBeSpacedCorrectly", Justification = "All current violations are due to Tuple shorthand and so valid.")]
-[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:ClosingSquareBracketsMustBeSpacedCorrectly", Justification = "Optional arrays need to be supported. Ex []?")]
-
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:PrefixLocalCallsWithThis", Justification = "We follow the C# Core Coding Style which avoids using `this` unless absolutely necessary.")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:UsingDirectivesMustBePlacedWithinNamespace", Justification = "We follow the C# Core Coding Style which puts using statements outside the namespace.")]
diff --git a/src/GitHubExtension/Client/Exceptions.cs b/src/GitHubExtension/Client/Exceptions.cs
deleted file mode 100644
index ef04f59..0000000
--- a/src/GitHubExtension/Client/Exceptions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace GitHubExtension.Client;
-
-public class InvalidUrlException : Exception
-{
- public InvalidUrlException()
- {
- }
-
- public InvalidUrlException(string message)
- : base(message)
- {
- }
-}
-
-public class InvalidGitHubUrlException : Exception
-{
- public InvalidGitHubUrlException()
- {
- }
-
- public InvalidGitHubUrlException(string message)
- : base(message)
- {
- }
-}
-
-public class InvalidApiException : Exception
-{
- public InvalidApiException()
- {
- }
-
- public InvalidApiException(string message)
- : base(message)
- {
- }
-}
diff --git a/src/GitHubExtension/Client/GithubClientProvider.cs b/src/GitHubExtension/Client/GithubClientProvider.cs
index 830bc53..bc53d0b 100644
--- a/src/GitHubExtension/Client/GithubClientProvider.cs
+++ b/src/GitHubExtension/Client/GithubClientProvider.cs
@@ -12,7 +12,7 @@ public class GitHubClientProvider
{
private readonly GitHubClient publicRepoClient;
- private static readonly object InstanceLock = new ();
+ private static readonly object InstanceLock = new();
private static GitHubClientProvider? _instance;
diff --git a/src/GitHubExtension/Client/InvalidApiException.cs b/src/GitHubExtension/Client/InvalidApiException.cs
new file mode 100644
index 0000000..7354738
--- /dev/null
+++ b/src/GitHubExtension/Client/InvalidApiException.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace GitHubExtension.Client;
+
+public class InvalidApiException : Exception
+{
+ public InvalidApiException()
+ {
+ }
+
+ public InvalidApiException(string message)
+ : base(message)
+ {
+ }
+}
diff --git a/src/GitHubExtension/Client/InvalidGitHubUrlException.cs b/src/GitHubExtension/Client/InvalidGitHubUrlException.cs
new file mode 100644
index 0000000..2084491
--- /dev/null
+++ b/src/GitHubExtension/Client/InvalidGitHubUrlException.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace GitHubExtension.Client;
+
+public class InvalidGitHubUrlException : Exception
+{
+ public InvalidGitHubUrlException()
+ {
+ }
+
+ public InvalidGitHubUrlException(string message)
+ : base(message)
+ {
+ }
+}
diff --git a/src/GitHubExtension/Client/InvalidUrlException.cs b/src/GitHubExtension/Client/InvalidUrlException.cs
new file mode 100644
index 0000000..44d0bd1
--- /dev/null
+++ b/src/GitHubExtension/Client/InvalidUrlException.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace GitHubExtension.Client;
+
+public class InvalidUrlException : Exception
+{
+ public InvalidUrlException()
+ {
+ }
+
+ public InvalidUrlException(string message)
+ : base(message)
+ {
+ }
+}
diff --git a/src/GitHubExtension/DataManager/Exceptions.cs b/src/GitHubExtension/DataManager/DataStoreInaccessibleException.cs
similarity index 57%
rename from src/GitHubExtension/DataManager/Exceptions.cs
rename to src/GitHubExtension/DataManager/DataStoreInaccessibleException.cs
index 3fd9078..d39f4f3 100644
--- a/src/GitHubExtension/DataManager/Exceptions.cs
+++ b/src/GitHubExtension/DataManager/DataStoreInaccessibleException.cs
@@ -3,18 +3,6 @@
namespace GitHubExtension;
-public class RepositoryNotFoundException : ApplicationException
-{
- public RepositoryNotFoundException()
- {
- }
-
- public RepositoryNotFoundException(string message)
- : base(message)
- {
- }
-}
-
public class DataStoreInaccessibleException : ApplicationException
{
public DataStoreInaccessibleException()
diff --git a/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs b/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs
index bb1a8c4..1fee039 100644
--- a/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs
+++ b/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs
@@ -1,9 +1,10 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Windows.DevHome.SDK;
namespace GitHubExtension;
+
public class DataStoreOperationParameters
{
// parameters for updating the data store.
diff --git a/src/GitHubExtension/DataManager/GitHubDataManager.cs b/src/GitHubExtension/DataManager/GitHubDataManager.cs
index c5b3ec3..36141fe 100644
--- a/src/GitHubExtension/DataManager/GitHubDataManager.cs
+++ b/src/GitHubExtension/DataManager/GitHubDataManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using GitHubExtension.Client;
@@ -18,6 +18,7 @@ public partial class GitHubDataManager : IGitHubDataManager, IDisposable
private static readonly TimeSpan SearchRetentionTime = TimeSpan.FromDays(7);
private static readonly TimeSpan PullRequestStaleTime = TimeSpan.FromDays(1);
private static readonly TimeSpan ReviewStaleTime = TimeSpan.FromDays(7);
+ private static readonly TimeSpan ReleaseRetentionTime = TimeSpan.FromDays(7);
// It is possible different widgets have queries which touch the same pull requests.
// We want to keep this window large enough that we don't delete data being used by
@@ -179,6 +180,28 @@ public async Task UpdatePullRequestsForLoggedInDeveloperIdsAsync()
SendDeveloperUpdateEvent(this);
}
+ public async Task UpdateReleasesForRepositoryAsync(string owner, string name, RequestOptions? options = null)
+ {
+ ValidateDataStore();
+ var parameters = new DataStoreOperationParameters
+ {
+ Owner = owner,
+ RepositoryName = name,
+ RequestOptions = options,
+ OperationName = "UpdateReleasesForRepositoryAsync",
+ };
+
+ await UpdateDataForRepositoryAsync(
+ parameters,
+ async (parameters, devId) =>
+ {
+ var repository = await UpdateRepositoryAsync(parameters.Owner!, parameters.RepositoryName!, devId.GitHubClient);
+ await UpdateReleasesAsync(repository, devId.GitHubClient, parameters.RequestOptions);
+ });
+
+ SendRepositoryUpdateEvent(this, GetFullNameFromOwnerAndRepository(owner, name), new string[] { "Releases" });
+ }
+
public IEnumerable GetRepositories()
{
ValidateDataStore();
@@ -269,29 +292,30 @@ private async Task UpdateDataForRepositoryAsync(DataStoreOperationParameters par
found = true;
break;
}
- catch (Exception ex)
+ catch (Exception ex) when (ex is Octokit.ApiException)
{
- if (ex is Octokit.ForbiddenException)
- {
- // This can happen most commonly with SAML-enabled organizations.
- Log.Logger()?.ReportDebug(Name, $"DeveloperId {devId.LoginId} was forbidden access to {parameters.Owner}/{parameters.RepositoryName}");
- continue;
- }
-
- if (ex is Octokit.NotFoundException)
- {
- // A private repository can come back as "not found" by the GitHub API when an unauthorized account cannot even view it.
- Log.Logger()?.ReportDebug(Name, $"DeveloperId {devId.LoginId} did not find {parameters.Owner}/{parameters.RepositoryName}");
- continue;
- }
-
- if (ex is Octokit.RateLimitExceededException)
+ switch (ex)
{
- Log.Logger()?.ReportError(Name, $"DeveloperId {devId.LoginId} rate limit exceeded.", ex);
- throw;
+ case Octokit.NotFoundException:
+ // A private repository will come back as "not found" by the GitHub API when an unauthorized account cannot even view it.
+ Log.Logger()?.ReportDebug(Name, $"DeveloperId {devId.LoginId} did not find {parameters.Owner}/{parameters.RepositoryName}");
+ continue;
+
+ case Octokit.RateLimitExceededException:
+ Log.Logger()?.ReportDebug(Name, $"DeveloperId {devId.LoginId} rate limit exceeded.");
+ throw;
+
+ case Octokit.ForbiddenException:
+ // This can happen most commonly with SAML-enabled organizations.
+ // The user may have access but the org blocked the application.
+ Log.Logger()?.ReportDebug(Name, $"DeveloperId {devId.LoginId} was forbidden access to {parameters.Owner}/{parameters.RepositoryName}");
+ throw;
+
+ default:
+ // If it's some other error like abuse detection, abort and do not continue.
+ Log.Logger()?.ReportDebug(Name, $"Unhandled Octokit API error for {devId.LoginId} and {parameters.Owner} / {parameters.RepositoryName}");
+ throw;
}
-
- throw;
}
}
@@ -703,6 +727,41 @@ private async Task UpdateIssuesAsync(Repository repository, Octokit.GitHubClient
Issue.DeleteLastObservedBefore(DataStore, repository.Id, DateTime.UtcNow - LastObservedDeleteSpan);
}
+ // Internal method to update releases. Assumes Repository has already been populated and created.
+ // DataStore transaction is assumed to be wrapped around this in the public method.
+ private async Task UpdateReleasesAsync(Repository repository, Octokit.GitHubClient? client = null, RequestOptions? options = null)
+ {
+ options ??= RequestOptions.RequestOptionsDefault();
+
+ // Limit the number of fetched releases.
+ options.ApiOptions.PageCount = 1;
+ options.ApiOptions.PageSize = 10;
+
+ client ??= await GitHubClientProvider.Instance.GetClientForLoggedInDeveloper(true);
+ Log.Logger()?.ReportInfo(Name, $"Updating releases for: {repository.FullName}");
+
+ var releasesResult = await client.Repository.Release.GetAll(repository.InternalId, options.ApiOptions);
+ if (releasesResult == null)
+ {
+ Log.Logger()?.ReportDebug($"No releases found.");
+ return;
+ }
+
+ Log.Logger()?.ReportDebug(Name, $"Results contain {releasesResult.Count} releases.");
+ foreach (var release in releasesResult)
+ {
+ if (release.Draft)
+ {
+ continue;
+ }
+
+ _ = Release.GetOrCreateByOctokitRelease(DataStore, release, repository);
+ }
+
+ // Remove releases from this repository that were not observed recently.
+ Release.DeleteLastObservedBefore(DataStore, repository.Id, DateTime.UtcNow - LastObservedDeleteSpan);
+ }
+
// Removes unused data from the datastore.
private void PruneObsoleteData()
{
@@ -714,6 +773,7 @@ private void PruneObsoleteData()
Search.DeleteBefore(DataStore, DateTime.Now - SearchRetentionTime);
SearchIssue.DeleteUnreferenced(DataStore);
Review.DeleteUnreferenced(DataStore);
+ Release.DeleteBefore(DataStore, DateTime.Now - ReleaseRetentionTime);
}
// Sets a last-updated in the MetaData.
@@ -763,7 +823,7 @@ private void ValidateDataStore()
// Making the default options a singleton to avoid repeatedly calling the storage APIs and
// creating a new GitHubDataStoreSchema when not necessary.
- private static readonly Lazy LazyDataStoreOptions = new (DefaultOptionsInit);
+ private static readonly Lazy LazyDataStoreOptions = new(DefaultOptionsInit);
private static DataStoreOptions DefaultOptions => LazyDataStoreOptions.Value;
diff --git a/src/GitHubExtension/DataManager/IGitHubDataManager.cs b/src/GitHubExtension/DataManager/IGitHubDataManager.cs
index a7f6e21..c6c981c 100644
--- a/src/GitHubExtension/DataManager/IGitHubDataManager.cs
+++ b/src/GitHubExtension/DataManager/IGitHubDataManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using GitHubExtension.DataModel;
@@ -25,6 +25,8 @@ public interface IGitHubDataManager : IDisposable
Task UpdatePullRequestsForLoggedInDeveloperIdsAsync();
+ Task UpdateReleasesForRepositoryAsync(string owner, string name, RequestOptions? options = null);
+
IEnumerable GetRepositories();
IEnumerable GetDeveloperUsers();
diff --git a/src/GitHubExtension/DataManager/RepositoryNotFoundException.cs b/src/GitHubExtension/DataManager/RepositoryNotFoundException.cs
new file mode 100644
index 0000000..d72de9b
--- /dev/null
+++ b/src/GitHubExtension/DataManager/RepositoryNotFoundException.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace GitHubExtension.DataManager;
+
+public class RepositoryNotFoundException : ApplicationException
+{
+ public RepositoryNotFoundException()
+ {
+ }
+
+ public RepositoryNotFoundException(string message)
+ : base(message)
+ {
+ }
+}
diff --git a/src/GitHubExtension/DataModel/DataObjects/Release.cs b/src/GitHubExtension/DataModel/DataObjects/Release.cs
new file mode 100644
index 0000000..55eca59
--- /dev/null
+++ b/src/GitHubExtension/DataModel/DataObjects/Release.cs
@@ -0,0 +1,161 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Dapper;
+using Dapper.Contrib.Extensions;
+using GitHubExtension.Helpers;
+
+namespace GitHubExtension.DataModel;
+
+[Table("Release")]
+public class Release
+{
+ [Key]
+ public long Id { get; set; } = DataStore.NoForeignKey;
+
+ public long InternalId { get; set; } = DataStore.NoForeignKey;
+
+ // Repository table
+ public long RepositoryId { get; set; } = DataStore.NoForeignKey;
+
+ public string Name { get; set; } = string.Empty;
+
+ public string TagName { get; set; } = string.Empty;
+
+ public long Prerelease { get; set; } = DataStore.NoForeignKey;
+
+ public string HtmlUrl { get; set; } = string.Empty;
+
+ public long TimeCreated { get; set; } = DataStore.NoForeignKey;
+
+ public long TimePublished { get; set; } = DataStore.NoForeignKey;
+
+ public long TimeLastObserved { get; set; } = DataStore.NoForeignKey;
+
+ [Write(false)]
+ private DataStore? DataStore
+ {
+ get; set;
+ }
+
+ [Write(false)]
+ [Computed]
+ public DateTime CreatedAt => TimeCreated.ToDateTime();
+
+ [Write(false)]
+ [Computed]
+ public DateTime? PublishedAt => TimePublished != 0 ? TimePublished.ToDateTime() : null;
+
+ [Write(false)]
+ [Computed]
+ public DateTime LastObservedAt => TimeLastObserved.ToDateTime();
+
+ public override string ToString() => Name;
+
+ public static Release GetOrCreateByOctokitRelease(DataStore dataStore, Octokit.Release okitRelease, Repository repository)
+ {
+ var release = CreateFromOctokitRelease(dataStore, okitRelease, repository);
+ return AddOrUpdateRelease(dataStore, release);
+ }
+
+ public static IEnumerable GetAllForRepository(DataStore dataStore, Repository repository)
+ {
+ var sql = $"SELECT * FROM Release WHERE RepositoryId = @RepositoryId ORDER BY TimePublished DESC;";
+ var param = new
+ {
+ RepositoryId = repository.Id,
+ };
+
+ Log.Logger()?.ReportDebug(DataStore.GetSqlLogMessage(sql, param));
+ var releases = dataStore.Connection!.Query(sql, param, null) ?? Enumerable.Empty();
+ foreach (var release in releases)
+ {
+ release.DataStore = dataStore;
+ }
+
+ return releases;
+ }
+
+ public static Release? GetByInternalId(DataStore dataStore, long internalId)
+ {
+ var sql = $"SELECT * FROM Release WHERE InternalId = @InternalId;";
+ var param = new
+ {
+ InternalId = internalId,
+ };
+
+ var release = dataStore.Connection!.QueryFirstOrDefault(sql, param, null);
+ if (release is not null)
+ {
+ // Add Datastore so this object can make internal queries.
+ release.DataStore = dataStore;
+ }
+
+ return release;
+ }
+
+ public static void DeleteLastObservedBefore(DataStore dataStore, long repositoryId, DateTime date)
+ {
+ // Delete releases older than the time specified for the given repository.
+ // This is intended to be run after updating a repository's releases so that non-observed
+ // records will be removed.
+ var sql = @"DELETE FROM Release WHERE RepositoryId = $RepositoryId AND TimeLastObserved < $Time;";
+ var command = dataStore.Connection!.CreateCommand();
+ command.CommandText = sql;
+ command.Parameters.AddWithValue("$Time", date.ToDataStoreInteger());
+ command.Parameters.AddWithValue("$RepositoryId", repositoryId);
+ Log.Logger()?.ReportDebug(DataStore.GetCommandLogMessage(sql, command));
+ var rowsDeleted = command.ExecuteNonQuery();
+ Log.Logger()?.ReportDebug(DataStore.GetDeletedLogMessage(rowsDeleted));
+ }
+
+ private static Release CreateFromOctokitRelease(DataStore dataStore, Octokit.Release okitRelease, Repository repository)
+ {
+ var release = new Release
+ {
+ DataStore = dataStore,
+ InternalId = okitRelease.Id,
+ RepositoryId = repository.Id,
+ Name = okitRelease.Name,
+ TagName = okitRelease.TagName,
+ Prerelease = okitRelease.Prerelease ? 1 : 0,
+ HtmlUrl = okitRelease.HtmlUrl,
+ TimeCreated = okitRelease.CreatedAt.DateTime.ToDataStoreInteger(),
+ TimePublished = okitRelease.PublishedAt.HasValue ? okitRelease.PublishedAt.Value.DateTime.ToDataStoreInteger() : 0,
+ TimeLastObserved = DateTime.UtcNow.ToDataStoreInteger(),
+ };
+
+ return release;
+ }
+
+ private static Release AddOrUpdateRelease(DataStore dataStore, Release release)
+ {
+ // Check for existing release data.
+ var existing = GetByInternalId(dataStore, release.InternalId);
+ if (existing is not null)
+ {
+ // Existing releases must be updated and always marked observed.
+ release.Id = existing.Id;
+ dataStore.Connection!.Update(release);
+ release.DataStore = dataStore;
+ return release;
+ }
+
+ // No existing release, add it.
+ release.Id = dataStore.Connection!.Insert(release);
+ release.DataStore = dataStore;
+ return release;
+ }
+
+ public static void DeleteBefore(DataStore dataStore, DateTime date)
+ {
+ // Delete releases older than the date listed.
+ var sql = @"DELETE FROM Release WHERE TimeLastObserved < $Time;";
+ var command = dataStore.Connection!.CreateCommand();
+ command.CommandText = sql;
+ command.Parameters.AddWithValue("$Time", date.ToDataStoreInteger());
+ Log.Logger()?.ReportDebug(DataStore.GetCommandLogMessage(sql, command));
+ var rowsDeleted = command.ExecuteNonQuery();
+ Log.Logger()?.ReportDebug(DataStore.GetDeletedLogMessage(rowsDeleted));
+ }
+}
diff --git a/src/GitHubExtension/DataModel/DataObjects/Repository.cs b/src/GitHubExtension/DataModel/DataObjects/Repository.cs
index f0fc3d8..2fbaf5b 100644
--- a/src/GitHubExtension/DataModel/DataObjects/Repository.cs
+++ b/src/GitHubExtension/DataModel/DataObjects/Repository.cs
@@ -109,6 +109,23 @@ public IEnumerable Issues
}
}
+ [Write(false)]
+ [Computed]
+ public IEnumerable Releases
+ {
+ get
+ {
+ if (DataStore == null)
+ {
+ return Enumerable.Empty();
+ }
+ else
+ {
+ return Release.GetAllForRepository(DataStore, this) ?? Enumerable.Empty();
+ }
+ }
+ }
+
public IEnumerable GetIssuesForQuery(string query)
{
if (DataStore == null)
diff --git a/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs b/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs
index 13fe1dd..440a64a 100644
--- a/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs
+++ b/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs
@@ -14,7 +14,7 @@ public GitHubDataStoreSchema()
}
// Update this anytime incompatible changes happen with a released version.
- private const long SchemaVersionValue = 0x0006;
+ private const long SchemaVersionValue = 0x0007;
private static readonly string Metadata =
@"CREATE TABLE Metadata (" +
@@ -248,8 +248,23 @@ public GitHubDataStoreSchema()
");" +
"CREATE UNIQUE INDEX IDX_Review_InternalId ON Review (InternalId);";
+ private static readonly string Release =
+ @"CREATE TABLE Release (" +
+ "Id INTEGER PRIMARY KEY NOT NULL," +
+ "InternalId INTEGER NOT NULL," +
+ "RepositoryId INTEGER NOT NULL," +
+ "Name TEXT NOT NULL COLLATE NOCASE," +
+ "TagName TEXT NOT NULL COLLATE NOCASE," +
+ "Prerelease INTEGER NOT NULL," +
+ "HtmlUrl TEXT NULL COLLATE NOCASE," +
+ "TimeCreated INTEGER NOT NULL," +
+ "TimePublished INTEGER NOT NULL," +
+ "TimeLastObserved INTEGER NOT NULL" +
+ ");" +
+ "CREATE UNIQUE INDEX IDX_Release_InternalId ON Release (InternalId);";
+
// All Sqls together.
- private static readonly List SchemaSqlsValue = new ()
+ private static readonly List SchemaSqlsValue = new()
{
Metadata,
User,
@@ -269,5 +284,6 @@ public GitHubDataStoreSchema()
Search,
SearchIssue,
Review,
+ Release,
};
}
diff --git a/src/GitHubExtension/DataModel/Logging.cs b/src/GitHubExtension/DataModel/Log.cs
similarity index 92%
rename from src/GitHubExtension/DataModel/Logging.cs
rename to src/GitHubExtension/DataModel/Log.cs
index 4898760..72b823c 100644
--- a/src/GitHubExtension/DataModel/Logging.cs
+++ b/src/GitHubExtension/DataModel/Log.cs
@@ -38,7 +38,7 @@ public static Options GetLoggingOptions()
return new Options
{
LogFileFolderRoot = ApplicationData.Current.TemporaryFolder.Path,
- LogFileName = "DataStore_{now}.log",
+ LogFileName = "DataStore_{now}.dhlog",
LogFileFolderName = "DataStore",
DebugListenerEnabled = true,
#if DEBUG
diff --git a/src/GitHubExtension/DeveloperId/CredentialVault.cs b/src/GitHubExtension/DeveloperId/CredentialVault.cs
index 99a678c..cfe453e 100644
--- a/src/GitHubExtension/DeveloperId/CredentialVault.cs
+++ b/src/GitHubExtension/DeveloperId/CredentialVault.cs
@@ -112,7 +112,7 @@ public void SaveCredentials(string loginId, SecureString? accessToken)
Marshal.Copy(credentialObject.CredentialBlob, accessTokenInChars, 0, accessTokenInChars.Length);
// convert accessTokenInChars to string
- string accessTokenString = new (accessTokenInChars);
+ string accessTokenString = new(accessTokenInChars);
for (var i = 0; i < accessTokenInChars.Length; i++)
{
diff --git a/src/GitHubExtension/DeveloperId/DeveloperId.cs b/src/GitHubExtension/DeveloperId/DeveloperId.cs
index 48fd4e8..c085e0d 100644
--- a/src/GitHubExtension/DeveloperId/DeveloperId.cs
+++ b/src/GitHubExtension/DeveloperId/DeveloperId.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Windows.DevHome.SDK;
@@ -26,7 +26,7 @@ public DeveloperId()
DisplayName = string.Empty;
Email = string.Empty;
Url = string.Empty;
- GitHubClient = new (new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME));
+ GitHubClient = new(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME));
}
public DeveloperId(string loginId, string displayName, string email, string url, GitHubClient gitHubClient)
@@ -65,7 +65,7 @@ public Windows.Security.Credentials.PasswordCredential RefreshDeveloperId()
CredentialExpiryTime = DateTime.MaxValue;
DeveloperIdProvider.GetInstance().RefreshDeveloperId(this);
var credential = DeveloperIdProvider.GetInstance().GetCredentials(this) ?? throw new InvalidOperationException("Invalid credential present for valid DeveloperId");
- GitHubClient.Credentials = new (credential.Password);
+ GitHubClient.Credentials = new(credential.Password);
return credential;
}
diff --git a/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs b/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs
index 566949f..a47f67f 100644
--- a/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs
+++ b/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs
@@ -14,9 +14,9 @@ namespace GitHubExtension.DeveloperId;
public class DeveloperIdProvider : IDeveloperIdProviderInternal
{
// Locks to control access to Singleton class members.
- private static readonly object _developerIdsLock = new ();
+ private static readonly object _developerIdsLock = new();
- private static readonly object _oAuthRequestsLock = new ();
+ private static readonly object _oAuthRequestsLock = new();
// DeveloperId list containing all Logged in Ids.
private List DeveloperIds
@@ -39,7 +39,7 @@ private List OAuthRequests
public string DisplayName => "GitHub";
// DeveloperIdProvider uses singleton pattern.
- private static Lazy _singletonDeveloperIdProvider = new (() => new DeveloperIdProvider());
+ private static Lazy _singletonDeveloperIdProvider = new(() => new DeveloperIdProvider());
public static DeveloperIdProvider GetInstance()
{
@@ -51,7 +51,7 @@ private DeveloperIdProvider()
{
Log.Logger()?.ReportInfo($"Creating DeveloperIdProvider singleton instance");
- _credentialVault = new (() => new CredentialVault());
+ _credentialVault = new(() => new CredentialVault());
lock (_oAuthRequestsLock)
{
@@ -76,7 +76,7 @@ private DeveloperIdProvider()
public DeveloperIdsResult GetLoggedInDeveloperIds()
{
- List iDeveloperIds = new ();
+ List iDeveloperIds = new();
lock (_developerIdsLock)
{
iDeveloperIds.AddRange(DeveloperIds);
@@ -113,11 +113,11 @@ public DeveloperId LoginNewDeveloperIdWithPAT(Uri hostAddress, SecureString pers
{
try
{
- GitHubClient gitHubClient = new (new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME), hostAddress);
+ GitHubClient gitHubClient = new(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME), hostAddress);
var credentials = new Credentials(new System.Net.NetworkCredential(string.Empty, personalAccessToken).Password);
gitHubClient.Credentials = credentials;
var newUser = gitHubClient.User.Current().Result;
- DeveloperId developerId = new (newUser.Login, newUser.Name, newUser.Email, newUser.Url, gitHubClient);
+ DeveloperId developerId = new(newUser.Login, newUser.Name, newUser.Email, newUser.Url, gitHubClient);
SaveOrOverwriteDeveloperId(developerId, personalAccessToken);
Log.Logger()?.ReportInfo($"{developerId.LoginId} logged in with PAT flow to {developerId.GetHostAddress()}");
@@ -133,7 +133,7 @@ public DeveloperId LoginNewDeveloperIdWithPAT(Uri hostAddress, SecureString pers
private OAuthRequest? LoginNewDeveloperId()
{
- OAuthRequest oauthRequest = new ();
+ OAuthRequest oauthRequest = new();
lock (_oAuthRequestsLock)
{
@@ -222,7 +222,7 @@ public void HandleOauthRedirection(Uri authorizationResponse)
public IEnumerable GetLoggedInDeveloperIdsInternal()
{
- List iDeveloperIds = new ();
+ List iDeveloperIds = new();
lock (_developerIdsLock)
{
iDeveloperIds.AddRange(DeveloperIds);
@@ -321,15 +321,15 @@ private void RestoreDeveloperIds(IEnumerable loginIdsAndUrls)
// For loginIds without URL, use GitHub.com as default.
var hostAddress = isUrl ? new Uri(loginIdOrUrl) : new Uri(Constants.GITHUB_COM_URL);
- GitHubClient gitHubClient = new (new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME), hostAddress)
+ GitHubClient gitHubClient = new(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME), hostAddress)
{
- Credentials = new (_credentialVault.Value.GetCredentials(loginIdOrUrl)?.Password),
+ Credentials = new(_credentialVault.Value.GetCredentials(loginIdOrUrl)?.Password),
};
try
{
var user = gitHubClient.User.Current().Result;
- DeveloperId developerId = new (user.Login, user.Name, user.Email, user.Url, gitHubClient);
+ DeveloperId developerId = new(user.Login, user.Name, user.Email, user.Url, gitHubClient);
lock (_developerIdsLock)
{
DeveloperIds.Add(developerId);
@@ -396,7 +396,7 @@ public void Dispose()
// This function is to be used for testing purposes only.
public static void ResetInstanceForTests()
{
- _singletonDeveloperIdProvider = new (() => new DeveloperIdProvider());
+ _singletonDeveloperIdProvider = new(() => new DeveloperIdProvider());
}
public IAsyncOperation ShowLogonSession(WindowId windowHandle) => throw new NotImplementedException();
diff --git a/src/GitHubExtension/DeveloperId/Logging.cs b/src/GitHubExtension/DeveloperId/Log.cs
similarity index 92%
rename from src/GitHubExtension/DeveloperId/Logging.cs
rename to src/GitHubExtension/DeveloperId/Log.cs
index 1d8e628..40b1cc8 100644
--- a/src/GitHubExtension/DeveloperId/Logging.cs
+++ b/src/GitHubExtension/DeveloperId/Log.cs
@@ -38,7 +38,7 @@ public static Options GetLoggingOptions()
return new Options
{
LogFileFolderRoot = ApplicationData.Current.TemporaryFolder.Path,
- LogFileName = "DeveloperId_{now}.log",
+ LogFileName = "DeveloperId_{now}.dhlog",
LogFileFolderName = "DeveloperId",
DebugListenerEnabled = true,
#if DEBUG
diff --git a/src/GitHubExtension/DeveloperId/OAuthRequest.cs b/src/GitHubExtension/DeveloperId/OAuthRequest.cs
index 826fd6e..2c0d390 100644
--- a/src/GitHubExtension/DeveloperId/OAuthRequest.cs
+++ b/src/GitHubExtension/DeveloperId/OAuthRequest.cs
@@ -23,8 +23,8 @@ internal DateTime StartTime
internal OAuthRequest()
{
- gitHubClient = new (new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME));
- oAuthCompleted = new (0);
+ gitHubClient = new(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME));
+ oAuthCompleted = new(0);
State = string.Empty;
}
@@ -133,7 +133,7 @@ internal DeveloperId RetrieveDeveloperId()
}
var newUser = gitHubClient.User.Current().Result;
- DeveloperId developerId = new (newUser.Login, newUser.Name, newUser.Email, newUser.Url, gitHubClient);
+ DeveloperId developerId = new(newUser.Login, newUser.Name, newUser.Email, newUser.Url, gitHubClient);
return developerId;
}
diff --git a/src/GitHubExtension/GitHubExtension.csproj b/src/GitHubExtension/GitHubExtension.csproj
index ebef53d..85cca9a 100644
--- a/src/GitHubExtension/GitHubExtension.csproj
+++ b/src/GitHubExtension/GitHubExtension.csproj
@@ -26,6 +26,8 @@
+
+
@@ -38,9 +40,15 @@
Always
+
+ Always
+
Always
+
+ Always
+
Always
@@ -80,7 +88,7 @@
-
+
@@ -137,6 +145,15 @@
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
Always
diff --git a/src/GitHubExtension/Helpers/EnumHelper.cs b/src/GitHubExtension/Helpers/EnumHelper.cs
index e0db3c4..f7d3500 100644
--- a/src/GitHubExtension/Helpers/EnumHelper.cs
+++ b/src/GitHubExtension/Helpers/EnumHelper.cs
@@ -12,7 +12,7 @@ public class EnumHelper
SearchCategory.Issues => "Issues",
SearchCategory.PullRequests => "PullRequests",
SearchCategory.IssuesAndPullRequests => "IssuesAndPullRequests",
- _ => "unknown"
+ _ => "unknown",
};
public static SearchCategory StringToSearchCategory(string value)
diff --git a/src/GitHubExtension/Helpers/IconLoader.cs b/src/GitHubExtension/Helpers/IconLoader.cs
index c6a1764..e10fe37 100644
--- a/src/GitHubExtension/Helpers/IconLoader.cs
+++ b/src/GitHubExtension/Helpers/IconLoader.cs
@@ -7,7 +7,7 @@ namespace GitHubExtension.Helpers;
public class IconLoader
{
- private static readonly Dictionary Base64ImageRegistry = new ();
+ private static readonly Dictionary Base64ImageRegistry = new();
public static string GetIconAsBase64(string filename)
{
diff --git a/src/GitHubExtension/Helpers/Resources.cs b/src/GitHubExtension/Helpers/Resources.cs
index e305170..0b8ce7b 100644
--- a/src/GitHubExtension/Helpers/Resources.cs
+++ b/src/GitHubExtension/Helpers/Resources.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using DevHome.Logging;
@@ -60,6 +60,7 @@ public static string[] GetWidgetResourceIdentifiers()
"Widget_Template/EmptyAssigned",
"Widget_Template/EmptyMentioned",
"Widget_Template/EmptyReviews",
+ "Widget_Template/EmptyReleases",
"Widget_Template/Pulls",
"Widget_Template/Issues",
"Widget_Template/Opened",
@@ -102,6 +103,8 @@ public static string[] GetWidgetResourceIdentifiers()
"Widget_Template_Tooltip/Save",
"Widget_Template_Tooltip/Cancel",
"Widget_Template/ChooseAccountPlaceholder",
+ "Widget_Template/Published",
+ "Widget_Template_Tooltip/OpenRelease",
};
}
}
diff --git a/src/GitHubExtension/Notifications/Logging.cs b/src/GitHubExtension/Notifications/Log.cs
similarity index 92%
rename from src/GitHubExtension/Notifications/Logging.cs
rename to src/GitHubExtension/Notifications/Log.cs
index 390d7ff..55d5670 100644
--- a/src/GitHubExtension/Notifications/Logging.cs
+++ b/src/GitHubExtension/Notifications/Log.cs
@@ -38,7 +38,7 @@ public static Options GetLoggingOptions()
return new Options
{
LogFileFolderRoot = ApplicationData.Current.TemporaryFolder.Path,
- LogFileName = "Notifications_{now}.log",
+ LogFileName = "Notifications_{now}.dhlog",
LogFileFolderName = "Notifications",
DebugListenerEnabled = true,
#if DEBUG
diff --git a/src/GitHubExtension/Providers/Logging.cs b/src/GitHubExtension/Providers/Log.cs
similarity index 91%
rename from src/GitHubExtension/Providers/Logging.cs
rename to src/GitHubExtension/Providers/Log.cs
index af5e21a..58fcb34 100644
--- a/src/GitHubExtension/Providers/Logging.cs
+++ b/src/GitHubExtension/Providers/Log.cs
@@ -29,7 +29,7 @@ public static Options GetLoggingOptions()
return new Options
{
LogFileFolderRoot = ApplicationData.Current.TemporaryFolder.Path,
- LogFileName = "GitHubExtension_{now}.log",
+ LogFileName = "GitHubExtension_{now}.dhlog",
LogFileFolderName = "GitHubExtension",
DebugListenerEnabled = true,
#if DEBUG
diff --git a/src/GitHubExtension/Providers/RepositoryProvider.cs b/src/GitHubExtension/Providers/RepositoryProvider.cs
index 47b1fc2..011b35a 100644
--- a/src/GitHubExtension/Providers/RepositoryProvider.cs
+++ b/src/GitHubExtension/Providers/RepositoryProvider.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using GitHubExtension.Client;
+using GitHubExtension.DataModel;
using GitHubExtension.DeveloperId;
using GitHubExtension.Helpers;
using Microsoft.Windows.DevHome.SDK;
@@ -55,6 +56,19 @@ public IAsyncOperation IsUriSupportedAsync(Uri uri,
}).AsAsyncOperation();
}
+ private Octokit.GitHubClient GetClient(IDeveloperId developerId)
+ {
+ if (developerId != null)
+ {
+ var loggedInDeveloperId = DeveloperId.DeveloperIdProvider.GetInstance().GetDeveloperIdInternal(developerId);
+ return loggedInDeveloperId.GitHubClient;
+ }
+ else
+ {
+ return GitHubClientProvider.Instance.GetClient();
+ }
+ }
+
IAsyncOperation IRepositoryProvider.GetRepositoriesAsync(IDeveloperId developerId)
{
return Task.Run(async () =>
@@ -64,14 +78,15 @@ IAsyncOperation IRepositoryProvider.GetRepositoriesAsync(IDe
var repositoryList = new List();
try
{
- ApiOptions apiOptions = new ()
+ ApiOptions apiOptions = new()
{
- PageSize = 50,
+ PageSize = 100,
PageCount = 1,
};
// Authenticate as the specified developer Id.
- var client = DeveloperIdProvider.GetInstance().GetDeveloperIdInternal(developerId).GitHubClient;
+ var client = GetClient(developerId);
+
var request = new RepositoryRequest
{
Sort = RepositorySort.Updated,
@@ -93,12 +108,17 @@ IAsyncOperation IRepositoryProvider.GetRepositoriesAsync(IDe
var getAllOrgReposTask = client.Repository.GetAllForCurrent(request, apiOptions);
var publicRepos = await getPublicReposTask;
+ publicRepos = publicRepos.OrderByDescending(x => x.UpdatedAt).ToList();
+
var privateRepos = await getPrivateReposTask;
+ privateRepos = privateRepos.OrderByDescending(x => x.UpdatedAt).ToList();
+
var orgRepos = await getAllOrgReposTask;
+ orgRepos = orgRepos.OrderByDescending(x => x.UpdatedAt).ToList();
var allRepos = publicRepos.Union(privateRepos).Union(orgRepos);
- foreach (var repository in allRepos.OrderByDescending(x => x.UpdatedAt))
+ foreach (var repository in allRepos)
{
repositoryList.Add(new DevHomeRepository(repository));
}
diff --git a/src/GitHubExtension/Providers/SettingsUIController.cs b/src/GitHubExtension/Providers/SettingsUIController.cs
index 6c8cfa3..a4b6fa7 100644
--- a/src/GitHubExtension/Providers/SettingsUIController.cs
+++ b/src/GitHubExtension/Providers/SettingsUIController.cs
@@ -13,7 +13,7 @@ internal class SettingsUIController : IExtensionAdaptiveCardSession
private static readonly string _notificationsEnabledString = "NotificationsEnabled";
private IExtensionAdaptiveCard? _settingsUI;
- private static readonly SettingsUITemplate _settingsUITemplate = new ();
+ private static readonly SettingsUITemplate _settingsUITemplate = new();
public void Dispose()
{
diff --git a/src/GitHubExtension/Strings/en-US/Resources.resw b/src/GitHubExtension/Strings/en-US/Resources.resw
index f3efd99..dde7ecd 100644
--- a/src/GitHubExtension/Strings/en-US/Resources.resw
+++ b/src/GitHubExtension/Strings/en-US/Resources.resw
@@ -1,4 +1,4 @@
-
+
@@ -86,4 +145,12 @@
GitHub (Preview)
The display name of our widgets provider
+
+ List of releases in a GitHub repository.
+ Description for widget that displays the releases of a repository
+
+
+ Releases
+ Title for widget that displays the releases of a repository
+
\ No newline at end of file
diff --git a/src/Logging/listeners/LogFileListenerOptions.cs b/src/Logging/listeners/LogFileListenerOptions.cs
index b30594b..557f8b6 100644
--- a/src/Logging/listeners/LogFileListenerOptions.cs
+++ b/src/Logging/listeners/LogFileListenerOptions.cs
@@ -5,7 +5,7 @@ namespace DevHome.Logging;
public partial class Options
{
- private const string LogFileNameDefault = "DevHomeGitHubExtension.log";
+ private const string LogFileNameDefault = "DevHomeGitHubExtension.dhlog";
private const string LogFileFolderNameDefault = "{now}";
public string LogFileName { get; set; } = LogFileNameDefault;
diff --git a/src/Logging/listeners/StdoutListener.cs b/src/Logging/listeners/StdoutListener.cs
index c8e8954..7f80c01 100644
--- a/src/Logging/listeners/StdoutListener.cs
+++ b/src/Logging/listeners/StdoutListener.cs
@@ -19,7 +19,7 @@ public class StdoutListener : ListenerBase
// Static lock object so different instances of the Stdout listener do not simultaneously write
// to stdout and have interleaved tearing of messages.
- private static readonly object _stdoutLock = new ();
+ private static readonly object _stdoutLock = new();
public StdoutListener(string name)
: base(name)
diff --git a/src/Logging/logger/SeverityLevel.cs b/src/Logging/logger/FailFast.cs
similarity index 100%
rename from src/Logging/logger/SeverityLevel.cs
rename to src/Logging/logger/FailFast.cs
diff --git a/src/Logging/logger/LogEvent.cs b/src/Logging/logger/LogEvent.cs
index a555a41..b1ef61d 100644
--- a/src/Logging/logger/LogEvent.cs
+++ b/src/Logging/logger/LogEvent.cs
@@ -47,7 +47,7 @@ public TimeSpan Elapsed
public static long NoElapsedTicks => -1L;
- public static TimeSpan NoElapsed => new (NoElapsedTicks);
+ public static TimeSpan NoElapsed => new(NoElapsedTicks);
public bool HasElapsed => Elapsed.Ticks >= 0;
@@ -68,7 +68,7 @@ private LogEvent(string source, string subSource, SeverityLevel severity, string
public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message, TimeSpan elapsed) => Create(source, subSource, severity, message, null, elapsed);
- public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message, Exception? exception, TimeSpan elapsed) => new (source, subSource, severity, message, exception!, elapsed);
+ public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message, Exception? exception, TimeSpan elapsed) => new(source, subSource, severity, message, exception!, elapsed);
public string FullSourceName
{
diff --git a/src/Logging/logger/Logger.cs b/src/Logging/logger/Logger.cs
index 1601c2a..932c79d 100644
--- a/src/Logging/logger/Logger.cs
+++ b/src/Logging/logger/Logger.cs
@@ -45,9 +45,9 @@ public Logger(string name, Options options)
Dispose();
}
- private readonly BlockingCollection eventQueue = new (new ConcurrentQueue());
+ private readonly BlockingCollection eventQueue = new(new ConcurrentQueue());
- private readonly ManualResetEvent processorCanceledEvent = new (true);
+ private readonly ManualResetEvent processorCanceledEvent = new(true);
private CancellationTokenSource? cancelTokenSource;
diff --git a/src/Telemetry/Logger.cs b/src/Telemetry/Logger.cs
index 21af0ee..4f8275f 100644
--- a/src/Telemetry/Logger.cs
+++ b/src/Telemetry/Logger.cs
@@ -37,48 +37,48 @@ internal class Logger : ILogger
/// Logs telemetry locally, but shouldn't upload it. Similar to an ETW event.
/// Should be the same as EventSourceOptions(), as Verbose is the default level.
///
- private static readonly EventSourceOptions LocalOption = new () { Level = EventLevel.Verbose };
+ private static readonly EventSourceOptions LocalOption = new() { Level = EventLevel.Verbose };
///
/// Logs error telemetry locally, but shouldn't upload it. Similar to an ETW event.
///
- private static readonly EventSourceOptions LocalErrorOption = new () { Level = EventLevel.Error };
+ private static readonly EventSourceOptions LocalErrorOption = new() { Level = EventLevel.Error };
///
/// Logs telemetry.
/// Currently this is at 0% sampling for both internal and external retail devices.
///
- private static readonly EventSourceOptions InfoOption = new () { Keywords = TelemetryEventSource.TelemetryKeyword };
+ private static readonly EventSourceOptions InfoOption = new() { Keywords = TelemetryEventSource.TelemetryKeyword };
///
/// Logs error telemetry.
/// Currently this is at 0% sampling for both internal and external retail devices.
///
- private static readonly EventSourceOptions InfoErrorOption = new () { Level = EventLevel.Error, Keywords = TelemetryEventSource.TelemetryKeyword };
+ private static readonly EventSourceOptions InfoErrorOption = new() { Level = EventLevel.Error, Keywords = TelemetryEventSource.TelemetryKeyword };
///
/// Logs measure telemetry.
/// This should be sent back on internal devices, and a small, sampled % of external retail devices.
///
- private static readonly EventSourceOptions MeasureOption = new () { Keywords = TelemetryEventSource.MeasuresKeyword };
+ private static readonly EventSourceOptions MeasureOption = new() { Keywords = TelemetryEventSource.MeasuresKeyword };
///
/// Logs measure error telemetry.
/// This should be sent back on internal devices, and a small, sampled % of external retail devices.
///
- private static readonly EventSourceOptions MeasureErrorOption = new () { Level = EventLevel.Error, Keywords = TelemetryEventSource.MeasuresKeyword };
+ private static readonly EventSourceOptions MeasureErrorOption = new() { Level = EventLevel.Error, Keywords = TelemetryEventSource.MeasuresKeyword };
///
/// Logs critical telemetry.
/// This should be sent back on all devices sampled at 100%.
///
- private static readonly EventSourceOptions CriticalDataOption = new () { Keywords = TelemetryEventSource.CriticalDataKeyword };
+ private static readonly EventSourceOptions CriticalDataOption = new() { Keywords = TelemetryEventSource.CriticalDataKeyword };
///
/// Logs critical error telemetry.
/// This should be sent back on all devices sampled at 100%.
///
- private static readonly EventSourceOptions CriticalDataErrorOption = new () { Level = EventLevel.Error, Keywords = TelemetryEventSource.CriticalDataKeyword };
+ private static readonly EventSourceOptions CriticalDataErrorOption = new() { Level = EventLevel.Error, Keywords = TelemetryEventSource.CriticalDataKeyword };
///
/// ActivityId so we can correlate all events in the same run
@@ -88,7 +88,7 @@ internal class Logger : ILogger
///
/// List of strings we should try removing for sensitivity reasons.
///
- private readonly List> sensitiveStrings = new ();
+ private readonly List> sensitiveStrings = new();
///
/// Initializes a new instance of the class.
diff --git a/src/Telemetry/LoggerFactory.cs b/src/Telemetry/LoggerFactory.cs
index 600c85a..8aa3ea9 100644
--- a/src/Telemetry/LoggerFactory.cs
+++ b/src/Telemetry/LoggerFactory.cs
@@ -8,7 +8,7 @@ namespace GitHubExtension.Telemetry;
/// This would be useful for future when we have updated interfaces for logger like ILogger2, ILogger3 and so on
public class LoggerFactory
{
- private static readonly object LockObj = new ();
+ private static readonly object LockObj = new();
private static Logger loggerInstance;
diff --git a/test/GitHubExtension/DataStore/DataObjectTests.cs b/test/GitHubExtension/DataStore/DataObjectTests.cs
index 8af7574..82ce2ab 100644
--- a/test/GitHubExtension/DataStore/DataObjectTests.cs
+++ b/test/GitHubExtension/DataStore/DataObjectTests.cs
@@ -594,4 +594,67 @@ public void ReadAndWriteReview()
testListener.PrintEventCounts();
Assert.AreEqual(false, testListener.FoundErrors());
}
+
+ [TestMethod]
+ [TestCategory("Unit")]
+ public void ReadAndWriteRelease()
+ {
+ using var log = new Logger("TestStore", TestOptions.LogOptions);
+ var testListener = new TestListener("TestListener", TestContext!);
+ log.AddListener(testListener);
+ Log.Attach(log);
+
+ using var dataStore = new DataStore("TestStore", TestHelpers.GetDataStoreFilePath(TestOptions), TestOptions.DataStoreOptions.DataStoreSchema!);
+ Assert.IsNotNull(dataStore);
+ dataStore.Create();
+ Assert.IsNotNull(dataStore.Connection);
+
+ // Add repository record
+ dataStore.Connection.Insert(new Repository { OwnerId = 1, InternalId = 47, Name = "TestRepo1", Description = "Short Desc", HtmlUrl = "https://www.microsoft.com", DefaultBranch = "main" });
+
+ var releases = new List
+ {
+ { new Release { InternalId = 13, Name = "Release 0.0.1", TagName = "0.0.1", Prerelease = 1, HtmlUrl = "https://www.microsoft.com", RepositoryId = 1 } },
+ { new Release { InternalId = 23, Name = "Release 1.0.0", TagName = "1.0.0", Prerelease = 0, HtmlUrl = "https://www.microsoft.com", RepositoryId = 1 } },
+ };
+
+ using var tx = dataStore.Connection!.BeginTransaction();
+ dataStore.Connection.Insert(releases[0]);
+ dataStore.Connection.Insert(releases[1]);
+ tx.Commit();
+
+ // Verify retrieval and input into data objects.
+ var dataStoreReleases = dataStore.Connection.GetAll().ToList();
+ Assert.AreEqual(dataStoreReleases.Count, 2);
+ foreach (var release in dataStoreReleases)
+ {
+ // Get Repo info
+ var repo = dataStore.Connection.Get(release.RepositoryId);
+
+ TestContext?.WriteLine($" Repo: {repo.Name} - {release.Name} - {release.TagName}");
+ Assert.AreEqual("TestRepo1", repo.Name);
+ Assert.IsTrue(release.Id == 1 || release.Id == 2);
+
+ if (release.Id == 1)
+ {
+ Assert.AreEqual(13, release.InternalId);
+ Assert.AreEqual("Release 0.0.1", release.Name);
+ Assert.AreEqual("0.0.1", release.TagName);
+ Assert.AreEqual(1, release.Prerelease);
+ Assert.AreEqual("https://www.microsoft.com", release.HtmlUrl);
+ }
+
+ if (release.Id == 2)
+ {
+ Assert.AreEqual(23, release.InternalId);
+ Assert.AreEqual("Release 1.0.0", release.Name);
+ Assert.AreEqual("1.0.0", release.TagName);
+ Assert.AreEqual(0, release.Prerelease);
+ Assert.AreEqual("https://www.microsoft.com", release.HtmlUrl);
+ }
+ }
+
+ testListener.PrintEventCounts();
+ Assert.AreEqual(false, testListener.FoundErrors());
+ }
}
diff --git a/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs b/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs
index 45e5b77..407cbc1 100644
--- a/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs
+++ b/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs
@@ -12,7 +12,7 @@ public TestContext? TestContext
set;
}
- private TestOptions testOptions = new ();
+ private TestOptions testOptions = new();
private TestOptions TestOptions
{
diff --git a/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs b/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs
index 950d82e..c0a0e8d 100644
--- a/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs
+++ b/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs
@@ -42,7 +42,7 @@ public TestContext? TestContext
set;
}
- private TestOptions testOptions = new ();
+ private TestOptions testOptions = new();
private TestOptions TestOptions
{
diff --git a/test/GitHubExtension/Helpers/TestSetupHelpers.cs b/test/GitHubExtension/Helpers/TestSetupHelpers.cs
index c7a4a91..e4a332d 100644
--- a/test/GitHubExtension/Helpers/TestSetupHelpers.cs
+++ b/test/GitHubExtension/Helpers/TestSetupHelpers.cs
@@ -10,7 +10,7 @@ namespace GitHubExtension.Test;
public partial class TestHelpers
{
private const string DataBaseFileName = "GitHubExtension-Test.db";
- private const string LogFileName = "GitHubExtension-{now}.log";
+ private const string LogFileName = "GitHubExtension-{now}.dhlog";
public static void CleanupTempTestOptions(TestOptions options, TestContext context)
{
diff --git a/test/GitHubExtension/MockExtensionAdaptiveCard.cs b/test/GitHubExtension/MockExtensionAdaptiveCard.cs
new file mode 100644
index 0000000..581eff5
--- /dev/null
+++ b/test/GitHubExtension/MockExtensionAdaptiveCard.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Windows.DevHome.SDK;
+
+namespace GitHubExtension.Test;
+
+public class MockExtensionAdaptiveCard : IExtensionAdaptiveCard
+{
+ private int updateCount;
+
+ public int UpdateCount
+ {
+ get => updateCount;
+ set => updateCount = value;
+ }
+
+ public MockExtensionAdaptiveCard(string templateJson, string dataJson, string state)
+ {
+ TemplateJson = templateJson;
+ DataJson = dataJson;
+ State = state;
+ }
+
+ public string DataJson
+ {
+ get; set;
+ }
+
+ public string State
+ {
+ get; set;
+ }
+
+ public string TemplateJson
+ {
+ get; set;
+ }
+
+ public ProviderOperationResult Update(string templateJson, string dataJson, string state)
+ {
+ UpdateCount++;
+ TemplateJson = templateJson;
+ DataJson = dataJson;
+ State = state;
+ return new ProviderOperationResult(ProviderOperationStatus.Success, null, "Update() succeeded", "Update() succeeded");
+ }
+}
diff --git a/test/GitHubExtension/Mocks/DeveloperIdProvider.cs b/test/GitHubExtension/Mocks/MockDeveloperIdProvider.cs
similarity index 69%
rename from test/GitHubExtension/Mocks/DeveloperIdProvider.cs
rename to test/GitHubExtension/Mocks/MockDeveloperIdProvider.cs
index 4800dfe..63b7b6b 100644
--- a/test/GitHubExtension/Mocks/DeveloperIdProvider.cs
+++ b/test/GitHubExtension/Mocks/MockDeveloperIdProvider.cs
@@ -27,7 +27,7 @@ public void Dispose()
public AuthenticationState GetDeveloperIdState(IDeveloperId developerId) => throw new NotImplementedException();
- public DeveloperIdsResult GetLoggedInDeveloperIds() => new (new List());
+ public DeveloperIdsResult GetLoggedInDeveloperIds() => new(new List());
public AdaptiveCardSessionResult GetLoginAdaptiveCardSession() => throw new NotImplementedException();
@@ -69,45 +69,3 @@ public static MockDeveloperIdProvider GetInstance()
public IEnumerable GetLoggedInDeveloperIdsInternal() => new List();
}
-
-public class MockExtensionAdaptiveCard : IExtensionAdaptiveCard
-{
- private int updateCount;
-
- public int UpdateCount
- {
- get => updateCount;
- set => updateCount = value;
- }
-
- public MockExtensionAdaptiveCard(string templateJson, string dataJson, string state)
- {
- TemplateJson = templateJson;
- DataJson = dataJson;
- State = state;
- }
-
- public string DataJson
- {
- get; set;
- }
-
- public string State
- {
- get; set;
- }
-
- public string TemplateJson
- {
- get; set;
- }
-
- public ProviderOperationResult Update(string templateJson, string dataJson, string state)
- {
- UpdateCount++;
- TemplateJson = templateJson;
- DataJson = dataJson;
- State = state;
- return new ProviderOperationResult(ProviderOperationStatus.Success, null, "Update() succeeded", "Update() succeeded");
- }
-}
diff --git a/test/GitHubExtension/Mocks/Repository.cs b/test/GitHubExtension/Mocks/MockRepository.cs
similarity index 100%
rename from test/GitHubExtension/Mocks/Repository.cs
rename to test/GitHubExtension/Mocks/MockRepository.cs
diff --git a/test/GitHubExtension/TestClass.cs b/test/GitHubExtension/TestClass.cs
index 1bb75d5..e86ec9d 100644
--- a/test/GitHubExtension/TestClass.cs
+++ b/test/GitHubExtension/TestClass.cs
@@ -50,7 +50,7 @@ private static void RemoveTestRepo()
}
}
- private static readonly Semaphore AuthenticationEventTriggered = new (initialCount: 0, maximumCount: 1);
+ private static readonly Semaphore AuthenticationEventTriggered = new(initialCount: 0, maximumCount: 1);
public void AuthenticationEvent(object? sender, IDeveloperId developerId)
{
diff --git a/test/GitHubExtension/Widgets/WidgetTestsSetup.cs b/test/GitHubExtension/Widgets/WidgetTestsSetup.cs
index a6eb4c8..a699992 100644
--- a/test/GitHubExtension/Widgets/WidgetTestsSetup.cs
+++ b/test/GitHubExtension/Widgets/WidgetTestsSetup.cs
@@ -12,7 +12,7 @@ public TestContext? TestContext
set;
}
- private TestOptions testOptions = new ();
+ private TestOptions testOptions = new();
private TestOptions TestOptions
{