diff --git a/.editorconfig b/.editorconfig index ecb4b867..984a626c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,8 @@ [*.cs] +file_header_template = Copyright (c) Microsoft Corporation.\r\nLicensed under the MIT License. + #Core editorconfig formatting - indentation #use soft tabs (spaces) for indentation diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d788d54a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=crlf diff --git a/.github/policies/moderatorTriggers.yml b/.github/policies/moderatorTriggers.yml index e9b92fd3..9b5608c0 100644 --- a/.github/policies/moderatorTriggers.yml +++ b/.github/policies/moderatorTriggers.yml @@ -78,7 +78,7 @@ configuration: if: - payloadType: Issue_Comment - commentContains: - pattern: '\/logs)' + pattern: '\/logs' isRegex: True - or: - activitySenderHasAssociation: @@ -98,9 +98,6 @@ configuration: repo names). Alternatively, you can open a Feedback Hub issue and attach them there. If you use Feedback Hub, please paste the URL at the bottom of the report here so we can easily find it. - - closeIssue - - removeLabel: - label: Needs-Triage - removeLabel: label: Needs-Team-Response # @@ -119,8 +116,6 @@ configuration: - activitySenderHasAssociation: association: Member then: - - removeLabel: - label: Needs-Triage - removeLabel: label: Needs-Team-Response - addLabel: diff --git a/Directory.Build.props b/Directory.Build.props index 58f53349..b4a1e118 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -39,7 +39,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/GitHubExtension.sln b/GitHubExtension.sln index 4082b7ed..8e7e8aa3 100644 --- a/GitHubExtension.sln +++ b/GitHubExtension.sln @@ -48,6 +48,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{E4D6 test\scripts\CleanWidgets.ps1 = test\scripts\CleanWidgets.ps1 EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{864DD9CD-9F45-47E8-847F-B72ED182626B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + exclusion.dic = exclusion.dic + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/TestingScenarios.md b/TestingScenarios.md index aeab785a..6ed4e2f3 100644 --- a/TestingScenarios.md +++ b/TestingScenarios.md @@ -1,10 +1,11 @@ ## Testing Scenarios -These are the testing scenarios that need to be validated before shipping a new release. When an automated test is added, please remove it from the below lists. +These are the testing scenarios that need to be validated before shipping a new release. When an automated test is added, please remove it from the below lists. ### Widgets -1. For each widget: user can configure the widget before pinning -1. For each widget: user can see widget in dashboard after pinning + +1. For each widget: user can configure the widget immediately after first pin +1. For each widget: user can customize (reconfigure) the widget after it was initially configured 1. For each widget that supports customization: can be customized and persist after restart 1. Widgets have correct data 1. Widgets are adequately performant @@ -12,4 +13,5 @@ These are the testing scenarios that need to be validated before shipping a new 1. Notifications are displayed ### Clone repositories -1. User can log in and get the list of repositories \ No newline at end of file + +1. User can log in and get the list of repositories diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index e74bbf09..e5c97b05 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -20,7 +20,7 @@ parameters: - release variables: - MSIXVersion: '0.1000' + MSIXVersion: '0.1100' solution: '**/GitHubExtension.sln' appxPackageDir: 'AppxPackages' testOutputArtifactDir: 'TestResults' diff --git a/build/scripts/Build.ps1 b/build/scripts/Build.ps1 index 4ae8b97d..dab04cdb 100644 --- a/build/scripts/Build.ps1 +++ b/build/scripts/Build.ps1 @@ -14,7 +14,7 @@ $StartTime = Get-Date if ($Help) { Write-Host @" -Copyright (c) Microsoft Corporation and Contributors. +Copyright (c) Microsoft Corporation. Licensed under the MIT License. Syntax: diff --git a/build/scripts/CreateBuildInfo.ps1 b/build/scripts/CreateBuildInfo.ps1 index 65dfcc7a..17a84fc2 100644 --- a/build/scripts/CreateBuildInfo.ps1 +++ b/build/scripts/CreateBuildInfo.ps1 @@ -5,7 +5,7 @@ Param( ) $Major = "0" -$Minor = "10" +$Minor = "11" $Patch = "99" # default to 99 for local builds $versionSplit = $Version.Split("."); diff --git a/build/scripts/Test.ps1 b/build/scripts/Test.ps1 index d637b443..b52c976f 100644 --- a/build/scripts/Test.ps1 +++ b/build/scripts/Test.ps1 @@ -9,7 +9,7 @@ $StartTime = Get-Date if ($Help) { Write-Host @" -Copyright (c) Microsoft Corporation and Contributors. +Copyright (c) Microsoft Corporation. Licensed under the MIT License. Syntax: diff --git a/build/scripts/Unstub.ps1 b/build/scripts/Unstub.ps1 index 792d749f..e6eac77d 100644 --- a/build/scripts/Unstub.ps1 +++ b/build/scripts/Unstub.ps1 @@ -10,12 +10,12 @@ if ($projFileContent.Contains('Microsoft.Telemetry.Inbox.Managed')) { return; } -$xml = [xml]$projFileContent +$xml = [System.Xml.XmlDocument]$projFileContent $xml.PreserveWhitespace = $true -$packageRef = $xml.SelectSingleNode("//ItemGroup/PackageReference") -$newNode = $packageRef.Clone() -$newNode.Include="Microsoft.Telemetry.Inbox.Managed" -$newNode.Version="10.0.25148.1001-220626-1600.rs-fun-deploy-dev5" -$parentNode = $packageRef.ParentNode -$parentNode.AppendChild($newNode) +$itemGroup = $xml.CreateElement("ItemGroup") +$packageRef = $xml.CreateElement("PackageReference") +$packageRef.SetAttribute("Include", "Microsoft.Telemetry.Inbox.Managed") +$packageRef.SetAttribute("Version", "10.0.25148.1001-220626-1600.rs-fun-deploy-dev5") +$itemGroup.AppendChild($packageRef) +$xml.Project.AppendChild($itemGroup) $xml.Save($projFile) \ No newline at end of file diff --git a/codeAnalysis/GlobalSuppressions.cs b/codeAnalysis/GlobalSuppressions.cs index 303231c7..6adc7ff0 100644 --- a/codeAnalysis/GlobalSuppressions.cs +++ b/codeAnalysis/GlobalSuppressions.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. @@ -13,10 +13,10 @@ [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.")] -[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "It is not a priority and have hight impact in code changes.")] -[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "It is not a priority and have hight impact in code changes.")] -[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1203:ConstantsMustAppearBeforeFields", Justification = "It is not a priority and have hight impact in code changes.")] -[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "It is not a priority and have hight impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "It is not a priority and has high impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "It is not a priority and has high impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1203:ConstantsMustAppearBeforeFields", Justification = "It is not a priority and has high impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "It is not a priority and has high impact in code changes.")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:FieldNamesMustNotBeginWithUnderscore", Justification = "We follow the C# Core Coding Style which uses underscores as prefixes rather than using `this.`.")] diff --git a/codeAnalysis/StyleCop.json b/codeAnalysis/StyleCop.json index bb7d02ca..f13fa14b 100644 --- a/codeAnalysis/StyleCop.json +++ b/codeAnalysis/StyleCop.json @@ -3,7 +3,7 @@ "settings": { "documentationRules": { "companyName": "Microsoft Corporation", - "copyrightText": "Copyright (c) Microsoft Corporation and Contributors\r\nLicensed under the MIT license.", + "copyrightText": "Copyright (c) Microsoft Corporation.\r\nLicensed under the MIT License.", "xmlHeader": false, "headerDecoration": "", "fileNamingConvention": "metadata", diff --git a/docs/specs/spec-template.md b/docs/specs/spec-template.md index 136cfb14..a7e6f54a 100644 --- a/docs/specs/spec-template.md +++ b/docs/specs/spec-template.md @@ -1,58 +1,58 @@ ---- -author: / -created on: -last updated: -issue id: ---- - -# Spec Title - -## Abstract - -[comment]: # Outline what this spec describes - -## Inspiration - -[comment]: # What were the drivers/inspiration behind the creation of this spec. - -## Solution Design - -[comment]: # Outline the design of the solution. Feel free to include ASCII-art diagrams, etc. - -## UI/UX Design - -[comment]: # What will this fix/feature look like? How will it affect the end user? - -## Capabilities - -[comment]: # Discuss how the proposed fixes/features impact the following key considerations: - -### Accessibility - -[comment]: # How will the proposed change impact accessibility for users of screen readers, assistive input devices, etc. - -### Security - -[comment]: # How will the proposed change impact security? - -### Reliability - -[comment]: # Will the proposed change improve reliability? If not, why make the change? - -### Compatibility - -[comment]: # Will the proposed change break existing code/behaviors? If so, how, and is the breaking change "worth it"? - -### Performance, Power, and Efficiency - -## Potential Issues - -[comment]: # What are some of the things that might cause problems with the fixes/features proposed? Consider how the user might be negatively impacted. - -## Future considerations - -[comment]: # What are some of the things that the fixes/features might unlock in the future? Does the implementation of this spec enable scenarios? - -## Resources - +--- +author: / +created on: +last updated: +issue id: +--- + +# Spec Title + +## Abstract + +[comment]: # Outline what this spec describes + +## Inspiration + +[comment]: # What were the drivers/inspiration behind the creation of this spec. + +## Solution Design + +[comment]: # Outline the design of the solution. Feel free to include ASCII-art diagrams, etc. + +## UI/UX Design + +[comment]: # What will this fix/feature look like? How will it affect the end user? + +## Capabilities + +[comment]: # Discuss how the proposed fixes/features impact the following key considerations: + +### Accessibility + +[comment]: # How will the proposed change impact accessibility for users of screen readers, assistive input devices, etc. + +### Security + +[comment]: # How will the proposed change impact security? + +### Reliability + +[comment]: # Will the proposed change improve reliability? If not, why make the change? + +### Compatibility + +[comment]: # Will the proposed change break existing code/behaviors? If so, how, and is the breaking change "worth it"? + +### Performance, Power, and Efficiency + +## Potential Issues + +[comment]: # What are some of the things that might cause problems with the fixes/features proposed? Consider how the user might be negatively impacted. + +## Future considerations + +[comment]: # What are some of the things that the fixes/features might unlock in the future? Does the implementation of this spec enable scenarios? + +## Resources + [comment]: # Be sure to add links to references, resources, footnotes, etc. \ No newline at end of file diff --git a/exclusion.dic b/exclusion.dic index d0e22042..548f38ae 100644 --- a/exclusion.dic +++ b/exclusion.dic @@ -1 +1,14 @@ devhome +enums +Octokit +advapi +Urls +Dependabot +github.com +inlines +abcd +Doggos +Stringify +riid +Impl +microsoft diff --git a/src/GitHubExtension/Client/Exceptions.cs b/src/GitHubExtension/Client/Exceptions.cs index bf265121..ef04f597 100644 --- a/src/GitHubExtension/Client/Exceptions.cs +++ b/src/GitHubExtension/Client/Exceptions.cs @@ -1,39 +1,40 @@ -// Copyright (c) Microsoft Corporation and Contributors -// 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) - { - } -} +// 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 02cd9109..830bc53c 100644 --- a/src/GitHubExtension/Client/GithubClientProvider.cs +++ b/src/GitHubExtension/Client/GithubClientProvider.cs @@ -1,99 +1,99 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using DevHome.Logging.Helpers; -using GitHubExtension.DeveloperId; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.Logging.Helpers; +using GitHubExtension.DeveloperId; using Microsoft.Windows.DevHome.SDK; -using Octokit; - -namespace GitHubExtension.Client; - -public class GitHubClientProvider -{ - private readonly GitHubClient publicRepoClient; - - private static readonly object InstanceLock = new (); - - private static GitHubClientProvider? _instance; - - public static GitHubClientProvider Instance - { - get - { - if (_instance == null) - { - lock (InstanceLock) - { - _instance = new GitHubClientProvider(); - } - } - - return _instance; - } - } - - public GitHubClientProvider() - { - publicRepoClient = new GitHubClient(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME)); - } - - public GitHubClient? GetClient(IDeveloperId devId) - { - var devIdInternal = DeveloperIdProvider.GetInstance().GetDeveloperIdInternal(devId) ?? throw new ArgumentException(devId.LoginId); - return devIdInternal.GitHubClient; - } - - public GitHubClient GetClient(string url) - { - var devIdInternal = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIdsInternal().Where(i => i.Url.Equals(url, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); - if (devIdInternal == null) - { - return publicRepoClient; - } - - return devIdInternal.GitHubClient; - } - - public GitHubClient GetClient() - { - return publicRepoClient; - } - - public async Task GetClientForLoggedInDeveloper(bool logRateLimit = false) - { - var authProvider = DeveloperIdProvider.GetInstance(); - var devIds = authProvider.GetLoggedInDeveloperIdsInternal(); - GitHubClient client; - if (devIds == null || !devIds.Any()) - { - Log.Logger()?.ReportInfo($"No logged in developer, using public GitHub client."); - client = Instance.GetClient(); - } - else - { - Log.Logger()?.ReportInfo($"Using authenticated user: {devIds.First().LoginId}"); - client = devIds.First().GitHubClient; - } - - if (client == null) - { - Log.Logger()?.ReportError($"Failed creating GitHubClient."); - return client!; - } - - if (logRateLimit) +using Octokit; + +namespace GitHubExtension.Client; + +public class GitHubClientProvider +{ + private readonly GitHubClient publicRepoClient; + + private static readonly object InstanceLock = new (); + + private static GitHubClientProvider? _instance; + + public static GitHubClientProvider Instance + { + get + { + if (_instance == null) + { + lock (InstanceLock) + { + _instance = new GitHubClientProvider(); + } + } + + return _instance; + } + } + + public GitHubClientProvider() + { + publicRepoClient = new GitHubClient(new ProductHeaderValue(Constants.DEV_HOME_APPLICATION_NAME)); + } + + public GitHubClient? GetClient(IDeveloperId devId) + { + var devIdInternal = DeveloperIdProvider.GetInstance().GetDeveloperIdInternal(devId) ?? throw new ArgumentException(devId.LoginId); + return devIdInternal.GitHubClient; + } + + public GitHubClient GetClient(string url) + { + var devIdInternal = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIdsInternal().Where(i => i.Url.Equals(url, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + if (devIdInternal == null) + { + return publicRepoClient; + } + + return devIdInternal.GitHubClient; + } + + public GitHubClient GetClient() + { + return publicRepoClient; + } + + public async Task GetClientForLoggedInDeveloper(bool logRateLimit = false) + { + var authProvider = DeveloperIdProvider.GetInstance(); + var devIds = authProvider.GetLoggedInDeveloperIdsInternal(); + GitHubClient client; + if (devIds == null || !devIds.Any()) + { + Log.Logger()?.ReportInfo($"No logged in developer, using public GitHub client."); + client = Instance.GetClient(); + } + else + { + Log.Logger()?.ReportInfo($"Using authenticated user: {devIds.First().LoginId}"); + client = devIds.First().GitHubClient; + } + + if (client == null) + { + Log.Logger()?.ReportError($"Failed creating GitHubClient."); + return client!; + } + + if (logRateLimit) { try { var miscRateLimit = await client.RateLimit.GetRateLimits(); Log.Logger()?.ReportInfo($"Rate Limit: Remaining: {miscRateLimit.Resources.Core.Remaining} Total: {miscRateLimit.Resources.Core.Limit} Resets: {miscRateLimit.Resources.Core.Reset.ToStringInvariant()}"); - } + } catch (Exception ex) { Log.Logger()?.ReportError($"Rate limiting not enabled for server.", ex); - } - } - - return client; - } -} + } + } + + return client; + } +} diff --git a/src/GitHubExtension/Client/Validation.cs b/src/GitHubExtension/Client/Validation.cs index d38bde43..24f14d5f 100644 --- a/src/GitHubExtension/Client/Validation.cs +++ b/src/GitHubExtension/Client/Validation.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using GitHubExtension.DataModel; using Octokit; diff --git a/src/GitHubExtension/Configuration/DeveloperOAuthConfiguration.cs b/src/GitHubExtension/Configuration/DeveloperOAuthConfiguration.cs index 040da66f..1eb28a66 100644 --- a/src/GitHubExtension/Configuration/DeveloperOAuthConfiguration.cs +++ b/src/GitHubExtension/Configuration/DeveloperOAuthConfiguration.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension; + internal static class DeveloperOAuthConfiguration { //// Follow this link https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app diff --git a/src/GitHubExtension/Configuration/OAuthConfiguration.cs b/src/GitHubExtension/Configuration/OAuthConfiguration.cs index 14f1d7b1..45841ba2 100644 --- a/src/GitHubExtension/Configuration/OAuthConfiguration.cs +++ b/src/GitHubExtension/Configuration/OAuthConfiguration.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension; + internal static class OauthConfiguration { // This redirect url has to be configured into the OAuth app. This package has "devhome://" diff --git a/src/GitHubExtension/Constants.cs b/src/GitHubExtension/Constants.cs index bdac2e0f..3728442f 100644 --- a/src/GitHubExtension/Constants.cs +++ b/src/GitHubExtension/Constants.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension; + internal class Constants { #pragma warning disable SA1310 // Field names should not contain underscore diff --git a/src/GitHubExtension/DataManager/DataManagerUpdateEventArgs.cs b/src/GitHubExtension/DataManager/DataManagerUpdateEventArgs.cs index b42abed9..25d468cd 100644 --- a/src/GitHubExtension/DataManager/DataManagerUpdateEventArgs.cs +++ b/src/GitHubExtension/DataManager/DataManagerUpdateEventArgs.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataManager; diff --git a/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs b/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs index 07f6b5a1..bb1a8c4a 100644 --- a/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs +++ b/src/GitHubExtension/DataManager/DataStoreOperationParameters.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.DevHome.SDK; diff --git a/src/GitHubExtension/DataManager/DataUpdater.cs b/src/GitHubExtension/DataManager/DataUpdater.cs index ff906d98..a2b9f5d8 100644 --- a/src/GitHubExtension/DataManager/DataUpdater.cs +++ b/src/GitHubExtension/DataManager/DataUpdater.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataManager; + public class DataUpdater : IDisposable { // This is the default interval the timer will run. It is not the interval that we necessarily do work. diff --git a/src/GitHubExtension/DataManager/Enums/SearchCategory.cs b/src/GitHubExtension/DataManager/Enums/SearchCategory.cs index 227faeaf..2cb0cc9c 100644 --- a/src/GitHubExtension/DataManager/Enums/SearchCategory.cs +++ b/src/GitHubExtension/DataManager/Enums/SearchCategory.cs @@ -1,12 +1,12 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace GitHubExtension.DataManager; - -public enum SearchCategory -{ - Issues, - PullRequests, - IssuesAndPullRequests, - Unknown, -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GitHubExtension.DataManager; + +public enum SearchCategory +{ + Issues, + PullRequests, + IssuesAndPullRequests, + Unknown, +} diff --git a/src/GitHubExtension/DataManager/Exceptions.cs b/src/GitHubExtension/DataManager/Exceptions.cs index 87f6f330..3fd90786 100644 --- a/src/GitHubExtension/DataManager/Exceptions.cs +++ b/src/GitHubExtension/DataManager/Exceptions.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension; + public class RepositoryNotFoundException : ApplicationException { public RepositoryNotFoundException() diff --git a/src/GitHubExtension/DataManager/GitHubDataManager.cs b/src/GitHubExtension/DataManager/GitHubDataManager.cs index 5d4cfee8..c5b3ec30 100644 --- a/src/GitHubExtension/DataManager/GitHubDataManager.cs +++ b/src/GitHubExtension/DataManager/GitHubDataManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Client; using GitHubExtension.DataManager; @@ -8,6 +8,7 @@ using Windows.Storage; namespace GitHubExtension; + public partial class GitHubDataManager : IGitHubDataManager, IDisposable { public static event DataManagerUpdateEventHandler? OnUpdate; @@ -16,6 +17,7 @@ public partial class GitHubDataManager : IGitHubDataManager, IDisposable private static readonly TimeSpan NotificationRetentionTime = TimeSpan.FromDays(7); private static readonly TimeSpan SearchRetentionTime = TimeSpan.FromDays(7); private static readonly TimeSpan PullRequestStaleTime = TimeSpan.FromDays(1); + private static readonly TimeSpan ReviewStaleTime = 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 @@ -438,6 +440,27 @@ private async Task UpdatePullRequestsForLoggedInDeveloperIdsAsync(DataStoreOpera CommitCombinedStatus.GetOrCreate(DataStore, commitCombinedStatus); CreatePullRequestStatus(dsPullRequest); + + // Review information for this pull request. + // We will only get review data for the logged-in Developer's pull requests. + try + { + var octoReviews = await devId.GitHubClient.PullRequest.Review.GetAll(repoName[0], repoName[1], octoPull.Number); + foreach (var octoReview in octoReviews) + { + ProcessReview(dsPullRequest, octoReview); + } + } + catch (Exception e) + { + // Octokit can sometimes fail unexpectedly or have bugs. Should that occur here, we + // will not stop processing all pull requests and instead skip over getting the PR + // review information for this particular pull request. + Log.Logger()?.ReportError($"Error updating Reviews for Pull Request #{octoPull.Number}: {e.Message}"); + + // Put the full stack trace in debug if this occurs to reduce log spam. + Log.Logger()?.ReportDebug($"Error updating Reviews for Pull Request #{octoPull.Number}.", e); + } } Log.Logger()?.ReportDebug(Name, $"Updated developer pull requests for {repoFullName}."); @@ -514,6 +537,36 @@ private async Task UpdatePullRequestsAsync(Repository repository, Octokit.GitHub PullRequest.DeleteLastObservedBefore(DataStore, repository.Id, DateTime.UtcNow - LastObservedDeleteSpan); } + private void ProcessReview(PullRequest pullRequest, Octokit.PullRequestReview octoReview) + { + // Skip reviews that are stale. + if ((DateTime.Now - octoReview.SubmittedAt) > ReviewStaleTime) + { + return; + } + + // For creating review notifications, must first determine if the review has changed. + var existingReview = Review.GetByInternalId(DataStore, octoReview.Id); + + // Add/update the review record. + var newReview = Review.GetOrCreateByOctokitReview(DataStore, octoReview, pullRequest.Id); + + // Ignore comments or pending state. + if (string.IsNullOrEmpty(newReview.State) || newReview.State == "Commented") + { + Log.Logger()?.ReportDebug(Name, "Notifications", $"Ignoring review for {pullRequest}. State: {newReview.State}"); + return; + } + + // Create a new notification if the state is different or the review did not exist. + if (existingReview == null || (existingReview.State != newReview.State)) + { + // We assume that the logged in developer created this pull request. + Log.Logger()?.ReportInfo(Name, "Notifications", $"Creating NewReview Notification for {pullRequest}. State: {newReview.State}"); + Notification.Create(DataStore, newReview, NotificationType.NewReview); + } + } + private void CreatePullRequestStatus(PullRequest pullRequest) { // Get the previous status for comparison. @@ -525,15 +578,13 @@ private void CreatePullRequestStatus(PullRequest pullRequest) if (ShouldCreateCheckFailureNotification(curStatus, prevStatus)) { Log.Logger()?.ReportInfo(Name, "Notifications", $"Creating CheckRunFailure Notification for {curStatus}"); - var notification = Notification.Create(curStatus, NotificationType.CheckRunFailed); - Notification.Add(DataStore, notification); + Notification.Create(DataStore, curStatus, NotificationType.CheckRunFailed); } if (ShouldCreateCheckSucceededNotification(curStatus, prevStatus)) { Log.Logger()?.ReportDebug(Name, "Notifications", $"Creating CheckRunSuccess Notification for {curStatus}"); - var notification = Notification.Create(curStatus, NotificationType.CheckRunSucceeded); - Notification.Add(DataStore, notification); + Notification.Create(DataStore, curStatus, NotificationType.CheckRunSucceeded); } } @@ -662,6 +713,7 @@ private void PruneObsoleteData() Notification.DeleteBefore(DataStore, DateTime.Now - NotificationRetentionTime); Search.DeleteBefore(DataStore, DateTime.Now - SearchRetentionTime); SearchIssue.DeleteUnreferenced(DataStore); + Review.DeleteUnreferenced(DataStore); } // Sets a last-updated in the MetaData. diff --git a/src/GitHubExtension/DataManager/GitHubDataManagerUpdate.cs b/src/GitHubExtension/DataManager/GitHubDataManagerUpdate.cs index 4c1a91f8..1082f6ff 100644 --- a/src/GitHubExtension/DataManager/GitHubDataManagerUpdate.cs +++ b/src/GitHubExtension/DataManager/GitHubDataManagerUpdate.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.DataManager; using GitHubExtension.DataModel; namespace GitHubExtension; + public partial class GitHubDataManager { // This is how frequently the DataStore update occurs. @@ -47,6 +48,12 @@ private static async Task UpdateDeveloperPullRequests() { notification.ShowToast(); } + + // Show notifications for new reviews. + if (notification.Type == NotificationType.NewReview) + { + notification.ShowToast(); + } } } diff --git a/src/GitHubExtension/DataManager/GitHubSearchManger.cs b/src/GitHubExtension/DataManager/GitHubSearchManger.cs index aff6bdae..7e00e996 100644 --- a/src/GitHubExtension/DataManager/GitHubSearchManger.cs +++ b/src/GitHubExtension/DataManager/GitHubSearchManger.cs @@ -1,98 +1,98 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using GitHubExtension.Client; -using GitHubExtension.DataManager; -using GitHubExtension.DataModel; -using Microsoft.Windows.DevHome.SDK; -using Octokit; - -namespace GitHubExtension; - -public delegate void SearchManagerResultsAvailableEventHandler(IEnumerable results, string resultType); - -public partial class GitHubSearchManager : IGitHubSearchManager, IDisposable -{ - private static readonly string Name = nameof(GitHubSearchManager); - - public static event SearchManagerResultsAvailableEventHandler? OnResultsAvailable; - - public GitHubSearchManager() - { - } - - public static IGitHubSearchManager? CreateInstance() - { - try - { - return new GitHubSearchManager(); - } - catch (Exception e) - { - Log.Logger()?.ReportError(Name, "Failed creating GitHubSearchManager", e); - Environment.FailFast(e.Message, e); - return null; - } - } - - public async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, IDeveloperId developerId, RequestOptions? options = null) - { - var client = GitHubClientProvider.Instance.GetClient(developerId.Url) ?? throw new InvalidOperationException($"Client does not exist for {developerId.Url}"); - - await SearchForGitHubIssuesOrPRs(request, initiator, category, client, options); - } - - public async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, RequestOptions? options = null) - { - var client = await GitHubClientProvider.Instance.GetClientForLoggedInDeveloper(true); - - await SearchForGitHubIssuesOrPRs(request, initiator, category, client, options); - } - - private async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, GitHubClient client, RequestOptions? options = null) - { - Log.Logger()?.ReportInfo(Name, $"Searching for issues or pull requests for widget {initiator}"); - request.State = Octokit.ItemState.Open; - request.Archived = false; - request.PerPage = 10; - request.SortField = Octokit.IssueSearchSort.Updated; - request.Order = Octokit.SortDirection.Descending; - - // Set is: parameter according to the search category. - // For the case we are searching for both we don't have to set the parameter - if (category.Equals(SearchCategory.Issues)) - { - request.Is = new List() { Octokit.IssueIsQualifier.Issue }; - } - else if (category.Equals(SearchCategory.PullRequests)) - { - request.Is = new List() { Octokit.IssueIsQualifier.PullRequest }; - } - - var octokitResult = await client.Search.SearchIssues(request); - if (octokitResult == null) - { - Log.Logger()?.ReportDebug($"No issues or PRs found."); - SendResultsAvailable(new List(), initiator); - } - else - { - Log.Logger()?.ReportDebug(Name, $"Results contain {octokitResult.Items.Count} items."); - SendResultsAvailable(octokitResult.Items, initiator); - } - } - - private void SendResultsAvailable(IEnumerable results, string initiator) - { - if (OnResultsAvailable != null) - { - Log.Logger()?.ReportInfo(Name, $"Sending search results available Event, of type: {initiator}"); - OnResultsAvailable.Invoke(results, initiator); - } - } - - public void Dispose() - { - GC.SuppressFinalize(this); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using GitHubExtension.Client; +using GitHubExtension.DataManager; +using GitHubExtension.DataModel; +using Microsoft.Windows.DevHome.SDK; +using Octokit; + +namespace GitHubExtension; + +public delegate void SearchManagerResultsAvailableEventHandler(IEnumerable results, string resultType); + +public partial class GitHubSearchManager : IGitHubSearchManager, IDisposable +{ + private static readonly string Name = nameof(GitHubSearchManager); + + public static event SearchManagerResultsAvailableEventHandler? OnResultsAvailable; + + public GitHubSearchManager() + { + } + + public static IGitHubSearchManager? CreateInstance() + { + try + { + return new GitHubSearchManager(); + } + catch (Exception e) + { + Log.Logger()?.ReportError(Name, "Failed creating GitHubSearchManager", e); + Environment.FailFast(e.Message, e); + return null; + } + } + + public async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, IDeveloperId developerId, RequestOptions? options = null) + { + var client = GitHubClientProvider.Instance.GetClient(developerId.Url) ?? throw new InvalidOperationException($"Client does not exist for {developerId.Url}"); + + await SearchForGitHubIssuesOrPRs(request, initiator, category, client, options); + } + + public async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, RequestOptions? options = null) + { + var client = await GitHubClientProvider.Instance.GetClientForLoggedInDeveloper(true); + + await SearchForGitHubIssuesOrPRs(request, initiator, category, client, options); + } + + private async Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, GitHubClient client, RequestOptions? options = null) + { + Log.Logger()?.ReportInfo(Name, $"Searching for issues or pull requests for widget {initiator}"); + request.State = Octokit.ItemState.Open; + request.Archived = false; + request.PerPage = 10; + request.SortField = Octokit.IssueSearchSort.Updated; + request.Order = Octokit.SortDirection.Descending; + + // Set is: parameter according to the search category. + // For the case we are searching for both we don't have to set the parameter + if (category.Equals(SearchCategory.Issues)) + { + request.Is = new List() { Octokit.IssueIsQualifier.Issue }; + } + else if (category.Equals(SearchCategory.PullRequests)) + { + request.Is = new List() { Octokit.IssueIsQualifier.PullRequest }; + } + + var octokitResult = await client.Search.SearchIssues(request); + if (octokitResult == null) + { + Log.Logger()?.ReportDebug($"No issues or PRs found."); + SendResultsAvailable(new List(), initiator); + } + else + { + Log.Logger()?.ReportDebug(Name, $"Results contain {octokitResult.Items.Count} items."); + SendResultsAvailable(octokitResult.Items, initiator); + } + } + + private void SendResultsAvailable(IEnumerable results, string initiator) + { + if (OnResultsAvailable != null) + { + Log.Logger()?.ReportInfo(Name, $"Sending search results available Event, of type: {initiator}"); + OnResultsAvailable.Invoke(results, initiator); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } +} diff --git a/src/GitHubExtension/DataManager/IGitHubDataManager.cs b/src/GitHubExtension/DataManager/IGitHubDataManager.cs index 9ec32318..a7f6e21e 100644 --- a/src/GitHubExtension/DataManager/IGitHubDataManager.cs +++ b/src/GitHubExtension/DataManager/IGitHubDataManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.DataModel; diff --git a/src/GitHubExtension/DataManager/IGitHubSearchManager.cs b/src/GitHubExtension/DataManager/IGitHubSearchManager.cs index 68514e8d..dbe24872 100644 --- a/src/GitHubExtension/DataManager/IGitHubSearchManager.cs +++ b/src/GitHubExtension/DataManager/IGitHubSearchManager.cs @@ -1,14 +1,14 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using GitHubExtension.DataManager; -using Microsoft.Windows.DevHome.SDK; - -namespace GitHubExtension; - -public interface IGitHubSearchManager : IDisposable -{ - Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, RequestOptions? options = null); - - Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, IDeveloperId developerId, RequestOptions? options = null); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using GitHubExtension.DataManager; +using Microsoft.Windows.DevHome.SDK; + +namespace GitHubExtension; + +public interface IGitHubSearchManager : IDisposable +{ + Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, RequestOptions? options = null); + + Task SearchForGitHubIssuesOrPRs(Octokit.SearchIssuesRequest request, string initiator, SearchCategory category, IDeveloperId developerId, RequestOptions? options = null); +} diff --git a/src/GitHubExtension/DataManager/RequestOptions.cs b/src/GitHubExtension/DataManager/RequestOptions.cs index aa69f232..3f5380e6 100644 --- a/src/GitHubExtension/DataManager/RequestOptions.cs +++ b/src/GitHubExtension/DataManager/RequestOptions.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Octokit; namespace GitHubExtension; + public class RequestOptions { // Request options for making queries to GitHub. diff --git a/src/GitHubExtension/DataModel/DataObjects/CheckRun.cs b/src/GitHubExtension/DataModel/DataObjects/CheckRun.cs index 482e6da8..0e2911d6 100644 --- a/src/GitHubExtension/DataModel/DataObjects/CheckRun.cs +++ b/src/GitHubExtension/DataModel/DataObjects/CheckRun.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/CheckSuite.cs b/src/GitHubExtension/DataModel/DataObjects/CheckSuite.cs index e846ac22..ef09e60d 100644 --- a/src/GitHubExtension/DataModel/DataObjects/CheckSuite.cs +++ b/src/GitHubExtension/DataModel/DataObjects/CheckSuite.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/CommitCombinedStatus.cs b/src/GitHubExtension/DataModel/DataObjects/CommitCombinedStatus.cs index fe19fe44..267d905e 100644 --- a/src/GitHubExtension/DataModel/DataObjects/CommitCombinedStatus.cs +++ b/src/GitHubExtension/DataModel/DataObjects/CommitCombinedStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/Issue.cs b/src/GitHubExtension/DataModel/DataObjects/Issue.cs index e735794a..8eadcc57 100644 --- a/src/GitHubExtension/DataModel/DataObjects/Issue.cs +++ b/src/GitHubExtension/DataModel/DataObjects/Issue.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/IssueAssign.cs b/src/GitHubExtension/DataModel/DataObjects/IssueAssign.cs index c4284b80..578ab91f 100644 --- a/src/GitHubExtension/DataModel/DataObjects/IssueAssign.cs +++ b/src/GitHubExtension/DataModel/DataObjects/IssueAssign.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/IssueLabel.cs b/src/GitHubExtension/DataModel/DataObjects/IssueLabel.cs index 01ef27bd..5ab44d4e 100644 --- a/src/GitHubExtension/DataModel/DataObjects/IssueLabel.cs +++ b/src/GitHubExtension/DataModel/DataObjects/IssueLabel.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/Label.cs b/src/GitHubExtension/DataModel/DataObjects/Label.cs index 7f738a4c..016f8363 100644 --- a/src/GitHubExtension/DataModel/DataObjects/Label.cs +++ b/src/GitHubExtension/DataModel/DataObjects/Label.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/MetaData.cs b/src/GitHubExtension/DataModel/DataObjects/MetaData.cs index b855ff9e..2a2e8781 100644 --- a/src/GitHubExtension/DataModel/DataObjects/MetaData.cs +++ b/src/GitHubExtension/DataModel/DataObjects/MetaData.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/Notification.cs b/src/GitHubExtension/DataModel/DataObjects/Notification.cs index 950c0f27..3f7e715c 100644 --- a/src/GitHubExtension/DataModel/DataObjects/Notification.cs +++ b/src/GitHubExtension/DataModel/DataObjects/Notification.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; @@ -161,6 +161,7 @@ public bool ShowToast() { NotificationType.CheckRunFailed => ShowFailedCheckRunToast(), NotificationType.CheckRunSucceeded => ShowSucceededCheckRunToast(), + NotificationType.NewReview => ShowNewReviewToast(), _ => false, }; } @@ -225,9 +226,52 @@ private bool ShowSucceededCheckRunToast() return true; } - public static Notification Create(PullRequestStatus status, NotificationType type) + private bool ShowNewReviewToast() { - return new Notification + try + { + Notifications.Log.Logger()?.ReportInfo($"Showing Notification for {this}"); + var resLoader = new ResourceLoader(ResourceLoader.GetDefaultResourceFilePath(), "GitHubExtension/Resources"); + var nb = new AppNotificationBuilder(); + nb.SetDuration(AppNotificationDuration.Long); + nb.AddArgument("htmlurl", HtmlUrl); + + switch (Result) + { + case "Approved": + nb.AddText($"✅ {resLoader.GetString("Notifications_Toast_NewReview/Approved")}"); + break; + + case "ChangesRequested": + nb.AddText($"⚠️ {resLoader.GetString("Notifications_Toast_NewReview/ChangesRequested")}"); + break; + + default: + throw new ArgumentException($"Unknown Review Result: {Result}"); + } + + nb.AddText($"#{Identifier} - {Repository.FullName}", new AppNotificationTextProperties().SetMaxLines(1)); + + // We want to show Author login but the AppNotification has a max 3 AddText calls, see: + // https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.windows.appnotifications.builder.appnotificationbuilder.addtext?view=windows-app-sdk-1.2 + // The newline is a workaround to the 3 line restriction to show the Author line. + nb.AddText(Title + Environment.NewLine + "@" + User.Login); + nb.AddButton(new AppNotificationButton(resLoader.GetString("Notifications_Toast_Button/Dismiss")).AddArgument("action", "dismiss")); + AppNotificationManager.Default.Show(nb.BuildNotification()); + Toasted = true; + } + catch (Exception ex) + { + Notifications.Log.Logger()?.ReportError($"Failed creating the Notification for {this}", ex); + return false; + } + + return true; + } + + public static Notification Create(DataStore dataStore, PullRequestStatus status, NotificationType type) + { + var pullRequestNotification = new Notification { TypeId = (long)type, UserId = status.PullRequest.AuthorId, @@ -242,6 +286,33 @@ public static Notification Create(PullRequestStatus status, NotificationType typ TimeOccurred = status.TimeOccurred, TimeCreated = DateTime.Now.ToDataStoreInteger(), }; + + Add(dataStore, pullRequestNotification); + SetOlderNotificationsToasted(dataStore, pullRequestNotification); + return pullRequestNotification; + } + + public static Notification Create(DataStore dataStore, Review review, NotificationType type) + { + var reviewNotification = new Notification + { + TypeId = (long)type, + UserId = review.AuthorId, + RepositoryId = review.PullRequest.RepositoryId, + Title = review.PullRequest.Title, + Description = review.Body, + Identifier = review.PullRequest.Number.ToStringInvariant(), + Result = review.State, + HtmlUrl = review.HtmlUrl, + DetailsUrl = review.HtmlUrl, + ToastState = 0, + TimeOccurred = review.TimeSubmitted, + TimeCreated = DateTime.Now.ToDataStoreInteger(), + }; + + Add(dataStore, reviewNotification); + SetOlderNotificationsToasted(dataStore, reviewNotification); + return reviewNotification; } public static Notification Add(DataStore dataStore, Notification notification) @@ -272,6 +343,30 @@ public static IEnumerable Get(DataStore dataStore, DateTime? since return notifications; } + public static void SetOlderNotificationsToasted(DataStore dataStore, Notification notification) + { + // Get all untoasted notifications for the same type, identifier, and author that are older + // than the specified notification. + var sql = @"SELECT * FROM Notification WHERE TypeId = @TypeId AND RepositoryId = @RepositoryId AND Identifier = @Identifier AND UserId = @UserId AND TimeOccurred < @TimeOccurred AND ToastState = 0"; + var param = new + { + notification.TypeId, + notification.RepositoryId, + notification.Identifier, + notification.UserId, + notification.TimeOccurred, + }; + + Log.Logger()?.ReportDebug(DataStore.GetSqlLogMessage(sql, param)); + var outDatedNotifications = dataStore.Connection!.Query(sql, param, null) ?? Enumerable.Empty(); + foreach (var olderNotification in outDatedNotifications) + { + olderNotification.DataStore = dataStore; + olderNotification.Toasted = true; + Notifications.Log.Logger()?.ReportInfo($"Found older notification for {olderNotification.Identifier} with result {olderNotification.Result}, marking toasted."); + } + } + public static void DeleteBefore(DataStore dataStore, DateTime date) { // Delete notifications older than the date listed. diff --git a/src/GitHubExtension/DataModel/DataObjects/PullRequest.cs b/src/GitHubExtension/DataModel/DataObjects/PullRequest.cs index 4ac02178..c07052c2 100644 --- a/src/GitHubExtension/DataModel/DataObjects/PullRequest.cs +++ b/src/GitHubExtension/DataModel/DataObjects/PullRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; @@ -317,6 +317,26 @@ public PullRequestStatus? PullRequestStatus } } + /// + /// Gets all reviews associated with this pull request. + /// + [Write(false)] + [Computed] + public IEnumerable Reviews + { + get + { + if (DataStore == null) + { + return Enumerable.Empty(); + } + else + { + return Review.GetAllForPullRequest(DataStore, this) ?? Enumerable.Empty(); + } + } + } + public override string ToString() => $"{Number}: {Title}"; // Create pull request from OctoKit pull request data @@ -365,7 +385,7 @@ private static PullRequest CreateFromOctokitPullRequest(DataStore dataStore, Oct pull.AssigneeIds = string.Join(",", assignees); - // Owner is a rowid in the User table + // Owner is a rowId in the User table var author = User.GetOrCreateByOctokitUser(dataStore, okitPull.User); pull.AuthorId = author.Id; diff --git a/src/GitHubExtension/DataModel/DataObjects/PullRequestAssign.cs b/src/GitHubExtension/DataModel/DataObjects/PullRequestAssign.cs index 1723fac4..3034cfe1 100644 --- a/src/GitHubExtension/DataModel/DataObjects/PullRequestAssign.cs +++ b/src/GitHubExtension/DataModel/DataObjects/PullRequestAssign.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/PullRequestLabel.cs b/src/GitHubExtension/DataModel/DataObjects/PullRequestLabel.cs index 28cc446a..ffd08077 100644 --- a/src/GitHubExtension/DataModel/DataObjects/PullRequestLabel.cs +++ b/src/GitHubExtension/DataModel/DataObjects/PullRequestLabel.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/PullRequestStatus.cs b/src/GitHubExtension/DataModel/DataObjects/PullRequestStatus.cs index 8fcbfe12..4de8ea59 100644 --- a/src/GitHubExtension/DataModel/DataObjects/PullRequestStatus.cs +++ b/src/GitHubExtension/DataModel/DataObjects/PullRequestStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/Repository.cs b/src/GitHubExtension/DataModel/DataObjects/Repository.cs index 61a79132..f0fc3d8f 100644 --- a/src/GitHubExtension/DataModel/DataObjects/Repository.cs +++ b/src/GitHubExtension/DataModel/DataObjects/Repository.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; @@ -149,7 +149,7 @@ private static Repository CreateFromOctokitRepository(DataStore dataStore, Octok TimePushed = octokitRepository.UpdatedAt.DateTime.ToDataStoreInteger(), }; - // Owner is a rowid in the User table + // Owner is a rowId in the User table var owner = User.GetOrCreateByOctokitUser(dataStore, octokitRepository.Owner); repo.OwnerId = owner.Id; @@ -250,12 +250,12 @@ public static IEnumerable GetAll(DataStore dataStore) return repo; } - public static Repository? Get(DataStore dataStore, string fullname) + public static Repository? Get(DataStore dataStore, string fullName) { - var nameSplit = fullname.Split(new[] { '/' }, 2); + var nameSplit = fullName.Split(new[] { '/' }, 2); if (nameSplit.Length != 2) { - Log.Logger()?.ReportWarn($"Invalid fullname input into Repository.Get: {fullname}"); + Log.Logger()?.ReportWarn($"Invalid fullName input into Repository.Get: {fullName}"); return null; } diff --git a/src/GitHubExtension/DataModel/DataObjects/Review.cs b/src/GitHubExtension/DataModel/DataObjects/Review.cs new file mode 100644 index 00000000..b3db32fb --- /dev/null +++ b/src/GitHubExtension/DataModel/DataObjects/Review.cs @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Dapper; +using Dapper.Contrib.Extensions; +using GitHubExtension.Helpers; + +namespace GitHubExtension.DataModel; + +[Table("Review")] +public class Review +{ + [Key] + public long Id { get; set; } = DataStore.NoForeignKey; + + public long InternalId { get; set; } = DataStore.NoForeignKey; + + // Pull request table + public long PullRequestId { get; set; } = DataStore.NoForeignKey; + + // User table + public long AuthorId { get; set; } = DataStore.NoForeignKey; + + public string Body { get; set; } = string.Empty; + + public string State { get; set; } = string.Empty; + + public string HtmlUrl { get; set; } = string.Empty; + + public long TimeSubmitted { get; set; } = DataStore.NoForeignKey; + + public long TimeLastObserved { get; set; } = DataStore.NoForeignKey; + + [Write(false)] + private DataStore? DataStore + { + get; set; + } + + [Write(false)] + [Computed] + public DateTime SubmittedAt => TimeSubmitted.ToDateTime(); + + [Write(false)] + [Computed] + public DateTime LastObservedAt => TimeLastObserved.ToDateTime(); + + [Write(false)] + [Computed] + public PullRequest PullRequest + { + get + { + if (DataStore == null) + { + return new PullRequest(); + } + else + { + return PullRequest.GetById(DataStore, PullRequestId) ?? new PullRequest(); + } + } + } + + [Write(false)] + [Computed] + public User Author + { + get + { + if (DataStore == null) + { + return new User(); + } + else + { + return User.GetById(DataStore, AuthorId) ?? new User(); + } + } + } + + public override string ToString() => $"{PullRequestId}: {AuthorId} - {State}"; + + // Create review from OctoKit review data + private static Review CreateFromOctokitReview(DataStore dataStore, Octokit.PullRequestReview okitReview, long pullRequestId) + { + var review = new Review + { + DataStore = dataStore, + InternalId = okitReview.Id, + Body = okitReview.Body ?? string.Empty, + State = okitReview.State.Value.ToString(), + HtmlUrl = okitReview.HtmlUrl ?? string.Empty, + TimeSubmitted = okitReview.SubmittedAt.DateTime.ToDataStoreInteger(), + TimeLastObserved = DateTime.UtcNow.ToDataStoreInteger(), + }; + + // Author is a rowid in the User table + var author = User.GetOrCreateByOctokitUser(dataStore, okitReview.User); + review.AuthorId = author.Id; + + // Repo is a row id in the Repository table. + // It is likely the case that we already know the repository id (such as when querying pulls for a repository). + if (pullRequestId != DataStore.NoForeignKey) + { + review.PullRequestId = pullRequestId; + } + + return review; + } + + private static Review AddOrUpdateReview(DataStore dataStore, Review review) + { + // Check for existing pull request data. + var existingReview = GetByInternalId(dataStore, review.InternalId); + if (existingReview is not null) + { + // Existing pull requests must always be updated to update the LastObserved time. + review.Id = existingReview.Id; + dataStore.Connection!.Update(review); + review.DataStore = dataStore; + return review; + } + + // No existing pull request, add it. + review.Id = dataStore.Connection!.Insert(review); + review.DataStore = dataStore; + return review; + } + + public static Review? GetById(DataStore dataStore, long id) + { + var review = dataStore.Connection!.Get(id); + if (review is not null) + { + // Add Datastore so this object can make internal queries. + review.DataStore = dataStore; + } + + return review; + } + + public static Review? GetByInternalId(DataStore dataStore, long internalId) + { + var sql = @"SELECT * FROM Review WHERE InternalId = @InternalId;"; + var param = new + { + InternalId = internalId, + }; + + var review = dataStore.Connection!.QueryFirstOrDefault(sql, param, null); + if (review is not null) + { + // Add Datastore so this object can make internal queries. + review.DataStore = dataStore; + } + + return review; + } + + public static Review GetOrCreateByOctokitReview(DataStore dataStore, Octokit.PullRequestReview octokitReview, long repositoryId = DataStore.NoForeignKey) + { + var newReview = CreateFromOctokitReview(dataStore, octokitReview, repositoryId); + return AddOrUpdateReview(dataStore, newReview); + } + + public static IEnumerable GetAllForPullRequest(DataStore dataStore, PullRequest pullRequest) + { + var sql = @"SELECT * FROM Review WHERE PullRequestId = @PullRequestId ORDER BY TimeSubmitted DESC;"; + var param = new + { + PullRequestId = pullRequest.Id, + }; + + Log.Logger()?.ReportDebug(DataStore.GetSqlLogMessage(sql, param)); + var reviews = dataStore.Connection!.Query(sql, param, null) ?? Enumerable.Empty(); + foreach (var review in reviews) + { + review.DataStore = dataStore; + } + + return reviews; + } + + public static IEnumerable GetAllForUser(DataStore dataStore, User user) + { + var sql = @"SELECT * FROM Review WHERE AuthorId = @AuthorId;"; + var param = new + { + AuthorId = user.Id, + }; + + Log.Logger()?.ReportDebug(DataStore.GetSqlLogMessage(sql, param)); + var reviews = dataStore.Connection!.Query(sql, param, null) ?? Enumerable.Empty(); + foreach (var review in reviews) + { + review.DataStore = dataStore; + } + + return reviews; + } + + public static void DeleteUnreferenced(DataStore dataStore) + { + // Delete any reviews that have no matching PullRequestId in the PullRequest table. + var sql = @"DELETE FROM Review WHERE PullRequestId NOT IN (SELECT Id FROM PullRequest)"; + var command = dataStore.Connection!.CreateCommand(); + command.CommandText = sql; + Log.Logger()?.ReportDebug(DataStore.GetCommandLogMessage(sql, command)); + var rowsDeleted = command.ExecuteNonQuery(); + Log.Logger()?.ReportDebug(DataStore.GetDeletedLogMessage(rowsDeleted)); + } +} diff --git a/src/GitHubExtension/DataModel/DataObjects/Search.cs b/src/GitHubExtension/DataModel/DataObjects/Search.cs index f64a1037..e445c6d6 100644 --- a/src/GitHubExtension/DataModel/DataObjects/Search.cs +++ b/src/GitHubExtension/DataModel/DataObjects/Search.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/SearchIssue.cs b/src/GitHubExtension/DataModel/DataObjects/SearchIssue.cs index 4d903252..7c226499 100644 --- a/src/GitHubExtension/DataModel/DataObjects/SearchIssue.cs +++ b/src/GitHubExtension/DataModel/DataObjects/SearchIssue.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataObjects/User.cs b/src/GitHubExtension/DataModel/DataObjects/User.cs index d00f0240..50407a71 100644 --- a/src/GitHubExtension/DataModel/DataObjects/User.cs +++ b/src/GitHubExtension/DataModel/DataObjects/User.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper; using Dapper.Contrib.Extensions; diff --git a/src/GitHubExtension/DataModel/DataStore.cs b/src/GitHubExtension/DataModel/DataStore.cs index 95169d86..dc6379a1 100644 --- a/src/GitHubExtension/DataModel/DataStore.cs +++ b/src/GitHubExtension/DataModel/DataStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; using System.Reflection; diff --git a/src/GitHubExtension/DataModel/DataStoreOptions.cs b/src/GitHubExtension/DataModel/DataStoreOptions.cs index 818c6b79..9929f445 100644 --- a/src/GitHubExtension/DataModel/DataStoreOptions.cs +++ b/src/GitHubExtension/DataModel/DataStoreOptions.cs @@ -1,27 +1,28 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace GitHubExtension.DataModel; -public partial class DataStoreOptions -{ - private const string DataStoreFileNameDefault = "GitHubData.db"; - - public string DataStoreFileName { get; set; } = DataStoreFileNameDefault; - - // The Temp Path is used for storage by default so tests can run this code without being packaged. - // If we directly put in the ApplicationData folder, it would fail anytime the program was not packaged. - // For use with packaged application, set in Options to: - // ApplicationData.Current.LocalFolder.Path - private readonly string dataStoreFolderPathDefault = Path.Combine(Path.GetTempPath(), "GitHubExtension"); - - // ApplicationData is not static, using a static folder for initialization. - private string? dataStoreFolderPath; - - public string DataStoreFolderPath - { - get => dataStoreFolderPath is null ? dataStoreFolderPathDefault : dataStoreFolderPath; - set => dataStoreFolderPath = string.IsNullOrEmpty(value) ? dataStoreFolderPathDefault : value; - } - - public IDataStoreSchema? DataStoreSchema { get; set; } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GitHubExtension.DataModel; + +public partial class DataStoreOptions +{ + private const string DataStoreFileNameDefault = "GitHubData.db"; + + public string DataStoreFileName { get; set; } = DataStoreFileNameDefault; + + // The Temp Path is used for storage by default so tests can run this code without being packaged. + // If we directly put in the ApplicationData folder, it would fail anytime the program was not packaged. + // For use with packaged application, set in Options to: + // ApplicationData.Current.LocalFolder.Path + private readonly string dataStoreFolderPathDefault = Path.Combine(Path.GetTempPath(), "GitHubExtension"); + + // ApplicationData is not static, using a static folder for initialization. + private string? dataStoreFolderPath; + + public string DataStoreFolderPath + { + get => dataStoreFolderPath is null ? dataStoreFolderPathDefault : dataStoreFolderPath; + set => dataStoreFolderPath = string.IsNullOrEmpty(value) ? dataStoreFolderPathDefault : value; + } + + public IDataStoreSchema? DataStoreSchema { get; set; } +} diff --git a/src/GitHubExtension/DataModel/DataStoreTransaction.cs b/src/GitHubExtension/DataModel/DataStoreTransaction.cs index bcc3c14d..12d49658 100644 --- a/src/GitHubExtension/DataModel/DataStoreTransaction.cs +++ b/src/GitHubExtension/DataModel/DataStoreTransaction.cs @@ -1,62 +1,63 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using Microsoft.Data.Sqlite; - -namespace GitHubExtension.DataModel; -public class DataStoreTransaction : IDataStoreTransaction -{ - private SqliteTransaction? transaction; - - public static IDataStoreTransaction BeginTransaction(DataStore dataStore) - { - if (dataStore != null) - { - if (dataStore.Connection != null) - { - return new DataStoreTransaction(dataStore.Connection.BeginTransaction()); - } - } - - return new DataStoreTransaction(null); - } - - private DataStoreTransaction(SqliteTransaction? tx) - { - transaction = tx; - } - - public void Commit() - { - transaction?.Commit(); - } - - public void Rollback() - { - transaction?.Rollback(); - } - - private bool disposed; // To detect redundant calls. - - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - if (disposing) - { - transaction?.Dispose(); - transaction = null; - } - - disposed = true; - } - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Data.Sqlite; + +namespace GitHubExtension.DataModel; + +public class DataStoreTransaction : IDataStoreTransaction +{ + private SqliteTransaction? transaction; + + public static IDataStoreTransaction BeginTransaction(DataStore dataStore) + { + if (dataStore != null) + { + if (dataStore.Connection != null) + { + return new DataStoreTransaction(dataStore.Connection.BeginTransaction()); + } + } + + return new DataStoreTransaction(null); + } + + private DataStoreTransaction(SqliteTransaction? tx) + { + transaction = tx; + } + + public void Commit() + { + transaction?.Commit(); + } + + public void Rollback() + { + transaction?.Rollback(); + } + + private bool disposed; // To detect redundant calls. + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + transaction?.Dispose(); + transaction = null; + } + + disposed = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/GitHubExtension/DataModel/Enums/CheckConclusion.cs b/src/GitHubExtension/DataModel/Enums/CheckConclusion.cs index 6c48c6ab..e1048d56 100644 --- a/src/GitHubExtension/DataModel/Enums/CheckConclusion.cs +++ b/src/GitHubExtension/DataModel/Enums/CheckConclusion.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; diff --git a/src/GitHubExtension/DataModel/Enums/CheckStatus.cs b/src/GitHubExtension/DataModel/Enums/CheckStatus.cs index dca3e598..a20774bf 100644 --- a/src/GitHubExtension/DataModel/Enums/CheckStatus.cs +++ b/src/GitHubExtension/DataModel/Enums/CheckStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; diff --git a/src/GitHubExtension/DataModel/Enums/CommitState.cs b/src/GitHubExtension/DataModel/Enums/CommitState.cs index fcbe67d7..0525dafa 100644 --- a/src/GitHubExtension/DataModel/Enums/CommitState.cs +++ b/src/GitHubExtension/DataModel/Enums/CommitState.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; diff --git a/src/GitHubExtension/DataModel/Enums/NotificationType.cs b/src/GitHubExtension/DataModel/Enums/NotificationType.cs index 9a4f54e0..bf35e15f 100644 --- a/src/GitHubExtension/DataModel/Enums/NotificationType.cs +++ b/src/GitHubExtension/DataModel/Enums/NotificationType.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; @@ -8,4 +8,5 @@ public enum NotificationType Unknown = 0, CheckRunFailed = 1, CheckRunSucceeded = 2, + NewReview = 3, } diff --git a/src/GitHubExtension/DataModel/Enums/PullRequestCombinedStatus.cs b/src/GitHubExtension/DataModel/Enums/PullRequestCombinedStatus.cs index 91f653fa..11ef8773 100644 --- a/src/GitHubExtension/DataModel/Enums/PullRequestCombinedStatus.cs +++ b/src/GitHubExtension/DataModel/Enums/PullRequestCombinedStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; diff --git a/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs b/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs index 9d586bde..13fe1dda 100644 --- a/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs +++ b/src/GitHubExtension/DataModel/GitHubDataStoreSchema.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; + public class GitHubDataStoreSchema : IDataStoreSchema { public long SchemaVersion => SchemaVersionValue; @@ -13,7 +14,7 @@ public GitHubDataStoreSchema() } // Update this anytime incompatible changes happen with a released version. - private const long SchemaVersionValue = 0x0005; + private const long SchemaVersionValue = 0x0006; private static readonly string Metadata = @"CREATE TABLE Metadata (" + @@ -233,6 +234,20 @@ public GitHubDataStoreSchema() ");" + "CREATE UNIQUE INDEX IDX_SearchIssue_SearchIssue ON SearchIssue (Search, Issue);"; + private static readonly string Review = + @"CREATE TABLE Review (" + + "Id INTEGER PRIMARY KEY NOT NULL," + + "InternalId INTEGER NOT NULL," + + "PullRequestId INTEGER NOT NULL," + + "AuthorId INTEGER NOT NULL," + + "Body TEXT NOT NULL COLLATE NOCASE," + + "State TEXT NOT NULL COLLATE NOCASE," + + "HtmlUrl TEXT NULL COLLATE NOCASE," + + "TimeSubmitted INTEGER NOT NULL," + + "TimeLastObserved INTEGER NOT NULL" + + ");" + + "CREATE UNIQUE INDEX IDX_Review_InternalId ON Review (InternalId);"; + // All Sqls together. private static readonly List SchemaSqlsValue = new () { @@ -253,5 +268,6 @@ public GitHubDataStoreSchema() Notification, Search, SearchIssue, + Review, }; } diff --git a/src/GitHubExtension/DataModel/IDataStoreSchema.cs b/src/GitHubExtension/DataModel/IDataStoreSchema.cs index db28e45d..b77a8691 100644 --- a/src/GitHubExtension/DataModel/IDataStoreSchema.cs +++ b/src/GitHubExtension/DataModel/IDataStoreSchema.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DataModel; diff --git a/src/GitHubExtension/DataModel/IDataStoreTransaction.cs b/src/GitHubExtension/DataModel/IDataStoreTransaction.cs index a6d42360..c302f9b2 100644 --- a/src/GitHubExtension/DataModel/IDataStoreTransaction.cs +++ b/src/GitHubExtension/DataModel/IDataStoreTransaction.cs @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace GitHubExtension.DataModel; - -public interface IDataStoreTransaction : IDisposable -{ - void Commit(); - - void Rollback(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GitHubExtension.DataModel; + +public interface IDataStoreTransaction : IDisposable +{ + void Commit(); + + void Rollback(); +} diff --git a/src/GitHubExtension/DataModel/Logging.cs b/src/GitHubExtension/DataModel/Logging.cs index 2a14568f..4898760a 100644 --- a/src/GitHubExtension/DataModel/Logging.cs +++ b/src/GitHubExtension/DataModel/Logging.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Windows.Storage; namespace GitHubExtension.DataModel; + public class Log { private static Logger? _logger; diff --git a/src/GitHubExtension/DeveloperId/CredentialManager.cs b/src/GitHubExtension/DeveloperId/CredentialManager.cs index 96835e0f..68f9a270 100644 --- a/src/GitHubExtension/DeveloperId/CredentialManager.cs +++ b/src/GitHubExtension/DeveloperId/CredentialManager.cs @@ -1,64 +1,65 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System.Runtime.InteropServices; - -namespace GitHubExtension.DeveloperId; -public static class CredentialManager -{ - public enum CRED_TYPE : int - { - GENERIC = 1, - DOMAIN_PASSWORD = 2, - DOMAIN_CERTIFICATE = 3, - DOMAIN_VISIBLE_PASSWORD = 4, - MAXIMUM = 5, - } - - public enum CRED_PERSIST : uint - { - Session = 1, - LocalMachine = 2, - Enterprise = 3, - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct CREDENTIAL - { - public int Flags; - public CRED_TYPE Type; - [MarshalAs(UnmanagedType.LPWStr)] - public string TargetName; - [MarshalAs(UnmanagedType.LPWStr)] - public string Comment; - public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; - public int CredentialBlobSize; - public IntPtr CredentialBlob; - public int Persist; - public int AttributeCount; - public IntPtr CredAttribute; - [MarshalAs(UnmanagedType.LPWStr)] - public string TargetAlias; - [MarshalAs(UnmanagedType.LPWStr)] - public string UserName; - } - - [DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CredWrite(CREDENTIAL credential, int flags); - - [DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CredDelete(string target, CRED_TYPE type, int flags); - - [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CredRead(string target, CRED_TYPE type, int flags, out IntPtr credential); - - [DllImport("advapi32.dll", EntryPoint = "CredEnumerateW", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CredEnumerate(string filter, uint flags, out uint count, out IntPtr credentials); - - [DllImport("advapi32.dll", EntryPoint = "CredFree", CharSet = CharSet.Unicode)] - internal static extern void CredFree(IntPtr buffer); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.InteropServices; + +namespace GitHubExtension.DeveloperId; + +public static class CredentialManager +{ + public enum CRED_TYPE : int + { + GENERIC = 1, + DOMAIN_PASSWORD = 2, + DOMAIN_CERTIFICATE = 3, + DOMAIN_VISIBLE_PASSWORD = 4, + MAXIMUM = 5, + } + + public enum CRED_PERSIST : uint + { + Session = 1, + LocalMachine = 2, + Enterprise = 3, + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CREDENTIAL + { + public int Flags; + public CRED_TYPE Type; + [MarshalAs(UnmanagedType.LPWStr)] + public string TargetName; + [MarshalAs(UnmanagedType.LPWStr)] + public string Comment; + public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; + public int CredentialBlobSize; + public IntPtr CredentialBlob; + public int Persist; + public int AttributeCount; + public IntPtr CredAttribute; + [MarshalAs(UnmanagedType.LPWStr)] + public string TargetAlias; + [MarshalAs(UnmanagedType.LPWStr)] + public string UserName; + } + + [DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CredWrite(CREDENTIAL credential, int flags); + + [DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CredDelete(string target, CRED_TYPE type, int flags); + + [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CredRead(string target, CRED_TYPE type, int flags, out IntPtr credential); + + [DllImport("advapi32.dll", EntryPoint = "CredEnumerateW", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CredEnumerate(string filter, uint flags, out uint count, out IntPtr credentials); + + [DllImport("advapi32.dll", EntryPoint = "CredFree", CharSet = CharSet.Unicode)] + internal static extern void CredFree(IntPtr buffer); +} diff --git a/src/GitHubExtension/DeveloperId/CredentialVault.cs b/src/GitHubExtension/DeveloperId/CredentialVault.cs index 3c3abcf1..99a678c6 100644 --- a/src/GitHubExtension/DeveloperId/CredentialVault.cs +++ b/src/GitHubExtension/DeveloperId/CredentialVault.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.ComponentModel; using System.Runtime.InteropServices; @@ -8,6 +8,7 @@ using static GitHubExtension.DeveloperId.CredentialManager; namespace GitHubExtension.DeveloperId; + public class CredentialVault : ICredentialVault { private readonly string _credentialResourceName; diff --git a/src/GitHubExtension/DeveloperId/DeveloperId.cs b/src/GitHubExtension/DeveloperId/DeveloperId.cs index f2a821d3..48fd4e82 100644 --- a/src/GitHubExtension/DeveloperId/DeveloperId.cs +++ b/src/GitHubExtension/DeveloperId/DeveloperId.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.DevHome.SDK; using Octokit; diff --git a/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs b/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs index 895ff03b..566949fa 100644 --- a/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs +++ b/src/GitHubExtension/DeveloperId/DeveloperIdProvider.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Net; using System.Security; diff --git a/src/GitHubExtension/DeveloperId/Enums/LoginUIState.cs b/src/GitHubExtension/DeveloperId/Enums/LoginUIState.cs index 2a340c82..eacdd596 100644 --- a/src/GitHubExtension/DeveloperId/Enums/LoginUIState.cs +++ b/src/GitHubExtension/DeveloperId/Enums/LoginUIState.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DeveloperId; diff --git a/src/GitHubExtension/DeveloperId/ICredentialVault.cs b/src/GitHubExtension/DeveloperId/ICredentialVault.cs index dd6a187b..b7ef13de 100644 --- a/src/GitHubExtension/DeveloperId/ICredentialVault.cs +++ b/src/GitHubExtension/DeveloperId/ICredentialVault.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Security; using Windows.Security.Credentials; diff --git a/src/GitHubExtension/DeveloperId/IDeveloperIdProviderInternal.cs b/src/GitHubExtension/DeveloperId/IDeveloperIdProviderInternal.cs index 1ac28961..26511720 100644 --- a/src/GitHubExtension/DeveloperId/IDeveloperIdProviderInternal.cs +++ b/src/GitHubExtension/DeveloperId/IDeveloperIdProviderInternal.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Security; using Microsoft.Windows.DevHome.SDK; diff --git a/src/GitHubExtension/DeveloperId/Logging.cs b/src/GitHubExtension/DeveloperId/Logging.cs index dae84d62..1d8e628f 100644 --- a/src/GitHubExtension/DeveloperId/Logging.cs +++ b/src/GitHubExtension/DeveloperId/Logging.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Windows.Storage; namespace GitHubExtension.DeveloperId; + public class Log { private static Logger? _logger; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/EndPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/EndPage.cs index c66895e7..51234cc6 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/EndPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/EndPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.DeveloperId.LoginUI; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPATPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPATPage.cs index de7bc318..b94740b5 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPATPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPATPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Security; using GitHubExtension.Helpers; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPage.cs index de2fb908..bc6842b2 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/EnterpriseServerPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/LoginFailedPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/LoginFailedPage.cs index 3d546869..a8144102 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/LoginFailedPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/LoginFailedPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/LoginPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/LoginPage.cs index cc7bd0bf..688ec225 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/LoginPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/LoginPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/LoginSucceededPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/LoginSucceededPage.cs index 942fbb0b..5ef03649 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/LoginSucceededPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/LoginSucceededPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; using Microsoft.Windows.DevHome.SDK; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/LoginUIPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/LoginUIPage.cs index 34c5a3ab..d6fe7453 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/LoginUIPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/LoginUIPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; using Microsoft.Windows.DevHome.SDK; diff --git a/src/GitHubExtension/DeveloperId/LoginUI/WaitingPage.cs b/src/GitHubExtension/DeveloperId/LoginUI/WaitingPage.cs index 7401ea06..19739c8b 100644 --- a/src/GitHubExtension/DeveloperId/LoginUI/WaitingPage.cs +++ b/src/GitHubExtension/DeveloperId/LoginUI/WaitingPage.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; diff --git a/src/GitHubExtension/DeveloperId/LoginUIController.cs b/src/GitHubExtension/DeveloperId/LoginUIController.cs index 419ef0aa..e29bb009 100644 --- a/src/GitHubExtension/DeveloperId/LoginUIController.cs +++ b/src/GitHubExtension/DeveloperId/LoginUIController.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Net; using GitHubExtension.Client; @@ -9,6 +9,7 @@ using Windows.Foundation; namespace GitHubExtension.DeveloperId; + public class LoginUIController : IExtensionAdaptiveCardSession { private readonly IDeveloperIdProviderInternal _developerIdProvider; @@ -60,15 +61,6 @@ public IAsyncOperation OnAction(string action, string i { try { - // If there is already a developer id, we should block another login. - if (_developerIdProvider.GetLoggedInDeveloperIdsInternal().Any()) - { - Log.Logger()?.ReportInfo($"DeveloperId {_developerIdProvider.GetLoggedInDeveloperIdsInternal().First().LoginId} already exists. Blocking login."); - new LoginFailedPage().UpdateExtensionAdaptiveCard(_loginUI); - operationResult = new ProviderOperationResult(ProviderOperationStatus.Failure, null, "Only one DeveloperId can be logged in at a time", "One DeveloperId already exists"); - break; - } - var loginPageActionPayload = Json.ToObject(action) ?? throw new InvalidOperationException("Invalid action"); if (!loginPageActionPayload.IsSubmitAction()) diff --git a/src/GitHubExtension/DeveloperId/OAuthRequest.cs b/src/GitHubExtension/DeveloperId/OAuthRequest.cs index b6d08435..826fd6e9 100644 --- a/src/GitHubExtension/DeveloperId/OAuthRequest.cs +++ b/src/GitHubExtension/DeveloperId/OAuthRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Net; using System.Security; @@ -9,6 +9,7 @@ using Octokit; namespace GitHubExtension.DeveloperId; + internal class OAuthRequest : IDisposable { internal string State { get; private set; } diff --git a/src/GitHubExtension/GitHubExtension.cs b/src/GitHubExtension/GitHubExtension.cs index 609ed651..9dd8204b 100644 --- a/src/GitHubExtension/GitHubExtension.cs +++ b/src/GitHubExtension/GitHubExtension.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.InteropServices; using GitHubExtension.DeveloperId; diff --git a/src/GitHubExtension/GitHubExtension.csproj b/src/GitHubExtension/GitHubExtension.csproj index b3839c54..15c68e57 100644 --- a/src/GitHubExtension/GitHubExtension.csproj +++ b/src/GitHubExtension/GitHubExtension.csproj @@ -1,11 +1,12 @@  - + Library enable enable Dev + portable @@ -70,27 +71,27 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - + + - + @@ -147,39 +148,8 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - $(DefineConstants);CANARY_BUILD $(DefineConstants);STABLE_BUILD - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - diff --git a/src/GitHubExtension/Helpers/DateTimeExtensions.cs b/src/GitHubExtension/Helpers/DateTimeExtensions.cs index 0462311b..f1236cbe 100644 --- a/src/GitHubExtension/Helpers/DateTimeExtensions.cs +++ b/src/GitHubExtension/Helpers/DateTimeExtensions.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; namespace GitHubExtension.Helpers; + public static class DateTimeExtensions { // Data store stores time as integers, which is just the Ticks so we don't lose precision. diff --git a/src/GitHubExtension/Helpers/EnumHelper.cs b/src/GitHubExtension/Helpers/EnumHelper.cs index ab8d3c9f..e0db3c49 100644 --- a/src/GitHubExtension/Helpers/EnumHelper.cs +++ b/src/GitHubExtension/Helpers/EnumHelper.cs @@ -1,29 +1,30 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using GitHubExtension.DataManager; - -namespace GitHubExtension.Helpers; -public class EnumHelper -{ - public static string SearchCategoryToString(SearchCategory searchCategory) => searchCategory switch - { - SearchCategory.Issues => "Issues", - SearchCategory.PullRequests => "PullRequests", - SearchCategory.IssuesAndPullRequests => "IssuesAndPullRequests", - _ => "unknown" - }; - - public static SearchCategory StringToSearchCategory(string value) - { - try - { - return Enum.Parse(value); - } - catch (Exception) - { - // Invalid value. - return SearchCategory.Unknown; - } - } -} +using GitHubExtension.DataManager; + +namespace GitHubExtension.Helpers; + +public class EnumHelper +{ + public static string SearchCategoryToString(SearchCategory searchCategory) => searchCategory switch + { + SearchCategory.Issues => "Issues", + SearchCategory.PullRequests => "PullRequests", + SearchCategory.IssuesAndPullRequests => "IssuesAndPullRequests", + _ => "unknown" + }; + + public static SearchCategory StringToSearchCategory(string value) + { + try + { + return Enum.Parse(value); + } + catch (Exception) + { + // Invalid value. + return SearchCategory.Unknown; + } + } +} diff --git a/src/GitHubExtension/Helpers/FileHelper.cs b/src/GitHubExtension/Helpers/FileHelper.cs index a2f16333..8580af20 100644 --- a/src/GitHubExtension/Helpers/FileHelper.cs +++ b/src/GitHubExtension/Helpers/FileHelper.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text; using Newtonsoft.Json; diff --git a/src/GitHubExtension/Helpers/IconLoader.cs b/src/GitHubExtension/Helpers/IconLoader.cs index ba2c870c..c6a17645 100644 --- a/src/GitHubExtension/Helpers/IconLoader.cs +++ b/src/GitHubExtension/Helpers/IconLoader.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.DataModel; namespace GitHubExtension.Helpers; + public class IconLoader { private static readonly Dictionary Base64ImageRegistry = new (); diff --git a/src/GitHubExtension/Helpers/Json.cs b/src/GitHubExtension/Helpers/Json.cs index ff3c48bd..314d3a9a 100644 --- a/src/GitHubExtension/Helpers/Json.cs +++ b/src/GitHubExtension/Helpers/Json.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/GitHubExtension/Helpers/LocalSettings.cs b/src/GitHubExtension/Helpers/LocalSettings.cs index 9aabb7bf..76563412 100644 --- a/src/GitHubExtension/Helpers/LocalSettings.cs +++ b/src/GitHubExtension/Helpers/LocalSettings.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Common.Services; using Windows.Storage; namespace GitHubExtension.Helpers; + public static class LocalSettings { private static readonly string _applicationDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DevHome/ApplicationData"); diff --git a/src/GitHubExtension/Helpers/Resources.cs b/src/GitHubExtension/Helpers/Resources.cs index 29b4df72..e3051708 100644 --- a/src/GitHubExtension/Helpers/Resources.cs +++ b/src/GitHubExtension/Helpers/Resources.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Microsoft.Windows.ApplicationModel.Resources; namespace GitHubExtension.Helpers; + public static class Resources { private static ResourceLoader? _resourceLoader; @@ -100,6 +101,7 @@ public static string[] GetWidgetResourceIdentifiers() "Widget_Template_Button/Cancel", "Widget_Template_Tooltip/Save", "Widget_Template_Tooltip/Cancel", + "Widget_Template/ChooseAccountPlaceholder", }; } } diff --git a/src/GitHubExtension/Helpers/RuntimeHelper.cs b/src/GitHubExtension/Helpers/RuntimeHelper.cs index 7cd3f578..59c9f95d 100644 --- a/src/GitHubExtension/Helpers/RuntimeHelper.cs +++ b/src/GitHubExtension/Helpers/RuntimeHelper.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Windows.Win32; using Windows.Win32.Foundation; diff --git a/src/GitHubExtension/Helpers/StringExtensions.cs b/src/GitHubExtension/Helpers/StringExtensions.cs index 59da8dba..9b8440ba 100644 --- a/src/GitHubExtension/Helpers/StringExtensions.cs +++ b/src/GitHubExtension/Helpers/StringExtensions.cs @@ -1,15 +1,16 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System.Globalization; - -namespace GitHubExtension.Helpers; -public static class StringExtensions -{ - public static string ToStringInvariant(this T value) => Convert.ToString(value, CultureInfo.InvariantCulture)!; - - public static string FormatInvariant(this string value, params object[] arguments) - { - return string.Format(CultureInfo.InvariantCulture, value, arguments); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Globalization; + +namespace GitHubExtension.Helpers; + +public static class StringExtensions +{ + public static string ToStringInvariant(this T value) => Convert.ToString(value, CultureInfo.InvariantCulture)!; + + public static string FormatInvariant(this string value, params object[] arguments) + { + return string.Format(CultureInfo.InvariantCulture, value, arguments); + } +} diff --git a/src/GitHubExtension/Helpers/TimeSpanHelper.cs b/src/GitHubExtension/Helpers/TimeSpanHelper.cs index fe6f8db6..9afbc566 100644 --- a/src/GitHubExtension/Helpers/TimeSpanHelper.cs +++ b/src/GitHubExtension/Helpers/TimeSpanHelper.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Jeffijoe.MessageFormat; namespace GitHubExtension.Helpers; + internal class TimeSpanHelper { public static string TimeSpanToDisplayString(TimeSpan timeSpan, Logger? log = null) diff --git a/src/GitHubExtension/NativeMethods.txt b/src/GitHubExtension/NativeMethods.txt index 820e3114..498e284c 100644 --- a/src/GitHubExtension/NativeMethods.txt +++ b/src/GitHubExtension/NativeMethods.txt @@ -1 +1 @@ -GetCurrentPackageFullName +GetCurrentPackageFullName diff --git a/src/GitHubExtension/Notifications/Logging.cs b/src/GitHubExtension/Notifications/Logging.cs index fe1d6a0a..390d7ff9 100644 --- a/src/GitHubExtension/Notifications/Logging.cs +++ b/src/GitHubExtension/Notifications/Logging.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Windows.Storage; namespace GitHubExtension.Notifications; + public class Log { private static Logger? _logger; diff --git a/src/GitHubExtension/Notifications/NotificationHandler.cs b/src/GitHubExtension/Notifications/NotificationHandler.cs index fef70865..c6626b7b 100644 --- a/src/GitHubExtension/Notifications/NotificationHandler.cs +++ b/src/GitHubExtension/Notifications/NotificationHandler.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; using System.Globalization; @@ -8,6 +8,7 @@ using Microsoft.Windows.AppNotifications; namespace GitHubExtension.Notifications; + public class NotificationHandler { #pragma warning disable IDE0060 // Remove unused parameter diff --git a/src/GitHubExtension/Notifications/NotificationManager.cs b/src/GitHubExtension/Notifications/NotificationManager.cs index 1155f6f2..3d9e3756 100644 --- a/src/GitHubExtension/Notifications/NotificationManager.cs +++ b/src/GitHubExtension/Notifications/NotificationManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.AppNotifications; diff --git a/src/GitHubExtension/Providers/DevHomeRepository.cs b/src/GitHubExtension/Providers/DevHomeRepository.cs index bc13040c..fe86428c 100644 --- a/src/GitHubExtension/Providers/DevHomeRepository.cs +++ b/src/GitHubExtension/Providers/DevHomeRepository.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Client; @@ -29,13 +29,13 @@ public class DevHomeRepository : Microsoft.Windows.DevHome.SDK.IRepository /// /// Initializes a new instance of the class. /// - /// The repository received from ocktokit - public DevHomeRepository(Octokit.Repository ocktokitRepository) + /// The repository received from octokit + public DevHomeRepository(Octokit.Repository octokitRepository) { - this.name = ocktokitRepository.Name; - this.cloneUrl = new Uri(ocktokitRepository.CloneUrl); + this.name = octokitRepository.Name; + this.cloneUrl = new Uri(octokitRepository.CloneUrl); - _lastUpdated = ocktokitRepository.UpdatedAt; - _isPrivate = ocktokitRepository.Private; + _lastUpdated = octokitRepository.UpdatedAt; + _isPrivate = octokitRepository.Private; } } diff --git a/src/GitHubExtension/Providers/Logging.cs b/src/GitHubExtension/Providers/Logging.cs index 938284e3..af5e21a7 100644 --- a/src/GitHubExtension/Providers/Logging.cs +++ b/src/GitHubExtension/Providers/Logging.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Windows.Storage; namespace GitHubExtension.Providers; + public class Log { private static Logger? _logger; diff --git a/src/GitHubExtension/Providers/RepositoryProvider.cs b/src/GitHubExtension/Providers/RepositoryProvider.cs index 463272d0..47b1fc2e 100644 --- a/src/GitHubExtension/Providers/RepositoryProvider.cs +++ b/src/GitHubExtension/Providers/RepositoryProvider.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Client; using GitHubExtension.DeveloperId; @@ -129,7 +129,7 @@ public IAsyncOperation GetRepositoryFromUriAsync(Uri uri, IDev return new RepositoryResult(exception, $"{exception.Message} HResult: {exception.HResult}"); } - Octokit.Repository? ocktokitRepo = null; + Octokit.Repository? octokitRepo = null; var owner = Validation.ParseOwnerFromGitHubURL(uri); var repoName = Validation.ParseRepositoryFromGitHubURL(uri); @@ -147,7 +147,7 @@ public IAsyncOperation GetRepositoryFromUriAsync(Uri uri, IDev gitHubClient = GitHubClientProvider.Instance.GetClient(); } - ocktokitRepo = gitHubClient.Repository.Get(owner, repoName).Result; + octokitRepo = gitHubClient.Repository.Get(owner, repoName).Result; } catch (AggregateException e) { @@ -176,13 +176,13 @@ public IAsyncOperation GetRepositoryFromUriAsync(Uri uri, IDev return new RepositoryResult(e, $"Unspecified error when cloning a repo. HResult: {e.HResult}"); } - if (ocktokitRepo == null) + if (octokitRepo == null) { return new RepositoryResult(new ArgumentException("Repo is still null"), "Repo is still null"); } else { - return new RepositoryResult(new DevHomeRepository(ocktokitRepo)); + return new RepositoryResult(new DevHomeRepository(octokitRepo)); } }).AsAsyncOperation(); } diff --git a/src/GitHubExtension/Providers/SettingsProvider.cs b/src/GitHubExtension/Providers/SettingsProvider.cs index 09ee8efc..420f6c51 100644 --- a/src/GitHubExtension/Providers/SettingsProvider.cs +++ b/src/GitHubExtension/Providers/SettingsProvider.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; using Microsoft.Windows.DevHome.SDK; diff --git a/src/GitHubExtension/Providers/SettingsUIController.cs b/src/GitHubExtension/Providers/SettingsUIController.cs index 98904cec..6c8cfa39 100644 --- a/src/GitHubExtension/Providers/SettingsUIController.cs +++ b/src/GitHubExtension/Providers/SettingsUIController.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Helpers; using Microsoft.Windows.ApplicationModel.Resources; @@ -7,6 +7,7 @@ using Windows.Foundation; namespace GitHubExtension.Providers; + internal class SettingsUIController : IExtensionAdaptiveCardSession { private static readonly string _notificationsEnabledString = "NotificationsEnabled"; diff --git a/src/GitHubExtension/Strings/en-US/Resources.resw b/src/GitHubExtension/Strings/en-US/Resources.resw index 9f74e894..f3efd99a 100644 --- a/src/GitHubExtension/Strings/en-US/Resources.resw +++ b/src/GitHubExtension/Strings/en-US/Resources.resw @@ -501,4 +501,16 @@ Please enter the PAT LoginUI Error text if input is null + + Approved + Shown in toast notification. + + + Changes requested + Shown in toast notification. + + + Please choose an account + Placeholder text for a combobox where the user can select between accounts + \ No newline at end of file diff --git a/src/GitHubExtension/Widgets/Enums/WidgetAction.cs b/src/GitHubExtension/Widgets/Enums/WidgetAction.cs index 671fc664..a732257d 100644 --- a/src/GitHubExtension/Widgets/Enums/WidgetAction.cs +++ b/src/GitHubExtension/Widgets/Enums/WidgetAction.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Widgets.Enums; + public enum WidgetAction { /// diff --git a/src/GitHubExtension/Widgets/Enums/WidgetActivityState.cs b/src/GitHubExtension/Widgets/Enums/WidgetActivityState.cs index 20ecab98..aa678140 100644 --- a/src/GitHubExtension/Widgets/Enums/WidgetActivityState.cs +++ b/src/GitHubExtension/Widgets/Enums/WidgetActivityState.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Widgets.Enums; + public enum WidgetActivityState { /// diff --git a/src/GitHubExtension/Widgets/Enums/WidgetDataState.cs b/src/GitHubExtension/Widgets/Enums/WidgetDataState.cs index 545f7be0..23ea2cca 100644 --- a/src/GitHubExtension/Widgets/Enums/WidgetDataState.cs +++ b/src/GitHubExtension/Widgets/Enums/WidgetDataState.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Widgets; + public enum WidgetDataState { Unknown, diff --git a/src/GitHubExtension/Widgets/Enums/WidgetPageState.cs b/src/GitHubExtension/Widgets/Enums/WidgetPageState.cs index 47b0a380..70e1bf7a 100644 --- a/src/GitHubExtension/Widgets/Enums/WidgetPageState.cs +++ b/src/GitHubExtension/Widgets/Enums/WidgetPageState.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Widgets; + public enum WidgetPageState { Unknown, diff --git a/src/GitHubExtension/Widgets/GitHubAssignedWidget.cs b/src/GitHubExtension/Widgets/GitHubAssignedWidget.cs index bdfd067f..45ea2655 100644 --- a/src/GitHubExtension/Widgets/GitHubAssignedWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubAssignedWidget.cs @@ -1,85 +1,86 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using Octokit; - -namespace GitHubExtension.Widgets; -internal class GitHubAssignedWidget : GitHubUserWidget -{ - private static readonly string TitleIconData = - "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAAlwS" + - "FlzAAAOwgAADsIBFShKgAAACXVJREFUeF7tmvuSFEUWh3kFDXAMQvJk9QChj7EhyMSsCLIwMR" + - "r7n4KgIHKTiyIIgqiIwIg6iOve3H2XZWZfafb3nc6srp5Ld1VP9whsn4gT1V15O3nuJys3jWE" + - "MYxjDGDYY7Eh43k6E3XYyvBY/Ep4Ke+xD/f8g7LJ3w3Op29MPbLS4GvYW98Kh1k8225pP+NAW" + - "Wj/b0gp86LjgfdS/eGAzxVf2ZjwbtqYpnw6IF8M2CG/NpY3+Tfj39Pyr8Nc+SL+MfxH+aAvFH" + - "ftTvGp7pSUvpGWePPCNz4UZEb7Y+ocIZ7NsAOQ3TOD9P4W/Cf9lS5MV9He0ZWbBDMbyzGN/ts" + - "V4PUymJZ8MkA2/6Bv/VRuH2F+EmXBQWlD8ICnet0NIsrhtB4qv7c0SpS3+/Ebvv1X7d3awmLM" + - "ZJO8alOfKzGSdhzKRa9ZKJPx+IMJ3th6JoLxpnu3fi8K3Wz/YTLxmb9h7tjkNqQ3xpE0UN2w/" + - "c2hO5uswF5Q2uGmcsIk0ZOPAjobNLC7VXnR1zYSh6j9J2p8PVzqKFhNohq/HGlVG3LfH8bS9m" + - "LqOHuLx8AKLOiEQkImZ18Zv2v74vm1JXYcO8aJtK+5pbdarmFshpsdPLKRuowPsvfUgLJSLZ/" + - "x+gwgQYE6ufW0za68PPWjeJduWug0fImoP96uSfyS8KxWUvaZuGwbFLdvhESfTwhNBjMoc3AZ" + - "ZJHtkFmXzR5s7uGGBMwFNqGik6Hxs79hwM0rZ9nZ3QHkhwtOd33fzGUrBZKEooyyu277UvH6I" + - "5+0lT1GZvCr54zZQVmZyonYmTMXzYRqHml4PDITB0iknGgtM4aMhmGU8rHxeki4lDwMG8Ljxt" + - "JKlW7bfk6EHclY/+jxLivELxV0lPl/YvnjYnk/dN9n7YUtxI+z35Ih64NPe6xVXrBBtnXwELV" + - "D+kZoHB4jokvy8JlZykpr7AgVRvKk5FCI9nQVhJtIif8gprv5ju3Jge+yw/aG4Gf7DmpP/Vpv" + - "SY5iUplwTyCKdzkSrxjyOHwympSUoHZ11Itk8kyoKWE27p4pDW3x8ZmDePP+p/sRQf887RRTN" + - "vySJL0nrKILa/Uilv+zPdEzK+7MGT4R1xfam5uZQXAmF236eEAKV6KTmnuCbJ5ev2KU/SZspd" + - "cn5Vd0Vn9nr1AFaZ1Z92ipMaM2Y163BALTNNS3TC+Nkdqm5OUgah5ygRBQqZTUdn2sOhOTxyu" + - "Wx/175gkxrO/2c+MywxIAoH5G69YTiS5lbLqASzQOZgR0LWyg2SmIahBbfSDVBQRKScmruCVp" + - "jsmQCGgAT2UjNteMFmQHONTFfVehS/NimUnN9UG0/7eqb1anmRPFyMI1pq3JCokiTalBEz5TM" + - "Y30YWNOWOWZj01U/E2Vmqbk+FF+Efb545iTZ1ZFOmFoLiltSwarTVJgT4xodacXrWhsHmZkgZ" + - "6hcZDo19wSvUhVmSwagPTXNpwQj9n8XHvviTIIEajg/aoXWPY3Ljo/FFcNTc21A+1TddZj/QA" + - "xQeEzNfcEPUyqRp0nYduCU1gnIDNDveLm/GsWrYe8KyX1if0zNtUERZMpDIIyEiXOa56S9lpr" + - "7As62iwFNNUA193S5eaFL4Gx/+/eF8+EIC5MzDOCBlTXu6WIAnrzBGcO6GUB89uwsb+R7MeBD" + - "252a1wSlt7POABbGbGrE7tUgfiwNQANhAPPctgOpqRasYEBjE1DI8dPZzID7tmTHbFdqXhXcb" + - "5D1VRnw+WC5eDwXptwEYQDht6EfcR+Q6YD+xibAQWTWAE1Cekp+nppXBRUvu0qppdAVB0xD4y" + - "WFYFLkzIAGmmTvhOeoBEsGYEJNBRHl8Vcw4EhvBvCJqyQ6M2CQ+CtQIdTO5piHON6k+DoaXm3" + - "JZ5UMEE2NHbEzIMfymhpA5BgaA6jqWJt52EADCXpBlDUR9Z8b4FyAvLvLCdbwASs0AMnVCJ3L" + - "IUqFtYEFl16WoJxyau4L8Vp4w0NxEkJTB+qwIgooDlufKDAsE8AB+niQ9RsyQAncQew+C2GgQ" + - "xEk5xtBAjBAYdBO9c7E/FsBksvjYMAAUcBT6Sz9hhoQT4QJ0ds+twRJxM4NUgip6HEuIgGIoB" + - "BScpSa14SuPADv27AW59hMRLfPHvPaMKAmIwtqCDae1/9Widi7A5wOUwrL7ttfX0BUqYYn9io" + - "uO080Rw7IGnwvIN6Xa2YGsHadOuR8eMmZxxgxQcJY4DA3NTcHP1jIxAj9MKTPURgHmN4/SxAn" + - "VFMLJL1JTIhSFqfLWJ8LSbK21Dt1XQEqgSdgdpfZruckCCiuqLBJm3fEni70NgO3wUxIhwmLH" + - "IYojK5ZSovZO7wfIUsxO55RJphL2szINQ5U1HcrJ8ul6tOf2uHYOr9P8jVWk7UdChOjijVOZf" + - "wcocoAxiJFFUbxU20u3QUib4hicnFXXpuTp2XnBpL6QTcnfFF7/QVCGqWy3y0iXebEmqyPduh" + - "kPb4HnBrSpzFJge/y7YkhhEMR+YfUvCp4Kqp+pToyjvH8p74gvOIo2Ry/wUeS/nXruvWh0rrg" + - "vW+sOkcel+eiPSF+a2ibB5B4aYttKdTSAr8cRUGSv9nl8VXM75HsLduRhnZB8VXYqX6dsLZ8j" + - "oyYj7LHQb9UrQn2XtjsnhWJJU1wO62ZWkqNX9HYP0tynU2ASJJD03mbLfp88WndsZe9LzQsnw" + - "PmKPIUo/wsTwjKm3duY8/y9qm5FhDfsXdSbK7LxEs23eS2F2aFzZNZkhOQqJHgcP6XuowOXJ3" + - "lVZ3jmQlSW23mybqpNUrgWoo2330RAdu9YdtTl2cfCrzy8mtwMOHm/xMTbttObbwTmtoasUgK" + - "i52mbs82OBOqmpAQPxGVkqZuzzYoCrSZkH1CRhIXxX95+ZHc1LJzYSvZXzzT7EvTSKDgG2D+F" + - "J0ZkOMz9wXT5+/1ZGZknRxxkXz5MRnnDZwzND3mHhVQ/JCfu1/IaWn2DUpcJtulMUXODNVlvB" + - "xe56uP1wPHwi4OMB35fVz1wWnVBxfCtNcI34QDrfnQvlyRL1BnZo/yKtwgoMTEVOwcIiqUWsA" + - "zmwjP/J6Mrh9Wx2fM4/lN4bRBlzIbAbc0lTrPitCV/qG6kdWw2pb7gp2xi9Kut7gh+kT4gF7g" + - "qSu2i3mQQP0mcwBzBQei0stR7+mT+v5X9v6Wl8Aqo7k1lqZ/uoAbm/GE7ZbUpqgByOe57MDXG" + - "k5sHfmNwyTXv6g+Z9V32JXdGMYwhjGMYQzLYNOm/wESzvR5z0LQ4QAAAABJRU5ErkJggg=="; - - protected static readonly new string Name = nameof(GitHubAssignedWidget); - - protected override string GetTitleIconData() - { - return TitleIconData; - } - - public override void RequestContentData() - { - var request = new SearchIssuesRequest() - { - Assignee = UserName, - }; - - RequestContentData(request); - } - - public override string GetTemplatePath(WidgetPageState page) - { - return page switch - { - WidgetPageState.SignIn => @"Widgets\Templates\GitHubSignInTemplate.json", - WidgetPageState.Configure => @"Widgets\Templates\GitHubAssignedConfigurationTemplate.json", - WidgetPageState.Content => @"Widgets\Templates\GitHubAssignedTemplate.json", - WidgetPageState.Loading => @"Widgets\Templates\GitHubLoadingTemplate.json", - _ => throw new NotImplementedException(), - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Octokit; + +namespace GitHubExtension.Widgets; + +internal class GitHubAssignedWidget : GitHubUserWidget +{ + private static readonly string TitleIconData = + "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAAlwS" + + "FlzAAAOwgAADsIBFShKgAAACXVJREFUeF7tmvuSFEUWh3kFDXAMQvJk9QChj7EhyMSsCLIwMR" + + "r7n4KgIHKTiyIIgqiIwIg6iOve3H2XZWZfafb3nc6srp5Ld1VP9whsn4gT1V15O3nuJys3jWE" + + "MYxjDGDYY7Eh43k6E3XYyvBY/Ep4Ke+xD/f8g7LJ3w3Op29MPbLS4GvYW98Kh1k8225pP+NAW" + + "Wj/b0gp86LjgfdS/eGAzxVf2ZjwbtqYpnw6IF8M2CG/NpY3+Tfj39Pyr8Nc+SL+MfxH+aAvFH" + + "ftTvGp7pSUvpGWePPCNz4UZEb7Y+ocIZ7NsAOQ3TOD9P4W/Cf9lS5MV9He0ZWbBDMbyzGN/ts" + + "V4PUymJZ8MkA2/6Bv/VRuH2F+EmXBQWlD8ICnet0NIsrhtB4qv7c0SpS3+/Ebvv1X7d3awmLM" + + "ZJO8alOfKzGSdhzKRa9ZKJPx+IMJ3th6JoLxpnu3fi8K3Wz/YTLxmb9h7tjkNqQ3xpE0UN2w/" + + "c2hO5uswF5Q2uGmcsIk0ZOPAjobNLC7VXnR1zYSh6j9J2p8PVzqKFhNohq/HGlVG3LfH8bS9m" + + "LqOHuLx8AKLOiEQkImZ18Zv2v74vm1JXYcO8aJtK+5pbdarmFshpsdPLKRuowPsvfUgLJSLZ/" + + "x+gwgQYE6ufW0za68PPWjeJduWug0fImoP96uSfyS8KxWUvaZuGwbFLdvhESfTwhNBjMoc3AZ" + + "ZJHtkFmXzR5s7uGGBMwFNqGik6Hxs79hwM0rZ9nZ3QHkhwtOd33fzGUrBZKEooyyu277UvH6I" + + "5+0lT1GZvCr54zZQVmZyonYmTMXzYRqHml4PDITB0iknGgtM4aMhmGU8rHxeki4lDwMG8Ljxt" + + "JKlW7bfk6EHclY/+jxLivELxV0lPl/YvnjYnk/dN9n7YUtxI+z35Ih64NPe6xVXrBBtnXwELV" + + "D+kZoHB4jokvy8JlZykpr7AgVRvKk5FCI9nQVhJtIif8gprv5ju3Jge+yw/aG4Gf7DmpP/Vpv" + + "SY5iUplwTyCKdzkSrxjyOHwympSUoHZ11Itk8kyoKWE27p4pDW3x8ZmDePP+p/sRQf887RRTN" + + "vySJL0nrKILa/Uilv+zPdEzK+7MGT4R1xfam5uZQXAmF236eEAKV6KTmnuCbJ5ev2KU/SZspd" + + "cn5Vd0Vn9nr1AFaZ1Z92ipMaM2Y163BALTNNS3TC+Nkdqm5OUgah5ygRBQqZTUdn2sOhOTxyu" + + "Wx/175gkxrO/2c+MywxIAoH5G69YTiS5lbLqASzQOZgR0LWyg2SmIahBbfSDVBQRKScmruCVp" + + "jsmQCGgAT2UjNteMFmQHONTFfVehS/NimUnN9UG0/7eqb1anmRPFyMI1pq3JCokiTalBEz5TM" + + "Y30YWNOWOWZj01U/E2Vmqbk+FF+Efb545iTZ1ZFOmFoLiltSwarTVJgT4xodacXrWhsHmZkgZ" + + "6hcZDo19wSvUhVmSwagPTXNpwQj9n8XHvviTIIEajg/aoXWPY3Ljo/FFcNTc21A+1TddZj/QA" + + "xQeEzNfcEPUyqRp0nYduCU1gnIDNDveLm/GsWrYe8KyX1if0zNtUERZMpDIIyEiXOa56S9lpr" + + "7As62iwFNNUA193S5eaFL4Gx/+/eF8+EIC5MzDOCBlTXu6WIAnrzBGcO6GUB89uwsb+R7MeBD" + + "252a1wSlt7POABbGbGrE7tUgfiwNQANhAPPctgOpqRasYEBjE1DI8dPZzID7tmTHbFdqXhXcb" + + "5D1VRnw+WC5eDwXptwEYQDht6EfcR+Q6YD+xibAQWTWAE1Cekp+nppXBRUvu0qppdAVB0xD4y" + + "WFYFLkzIAGmmTvhOeoBEsGYEJNBRHl8Vcw4EhvBvCJqyQ6M2CQ+CtQIdTO5piHON6k+DoaXm3" + + "JZ5UMEE2NHbEzIMfymhpA5BgaA6jqWJt52EADCXpBlDUR9Z8b4FyAvLvLCdbwASs0AMnVCJ3L" + + "IUqFtYEFl16WoJxyau4L8Vp4w0NxEkJTB+qwIgooDlufKDAsE8AB+niQ9RsyQAncQew+C2GgQ" + + "xEk5xtBAjBAYdBO9c7E/FsBksvjYMAAUcBT6Sz9hhoQT4QJ0ds+twRJxM4NUgip6HEuIgGIoB" + + "BScpSa14SuPADv27AW59hMRLfPHvPaMKAmIwtqCDae1/9Widi7A5wOUwrL7ttfX0BUqYYn9io" + + "uO080Rw7IGnwvIN6Xa2YGsHadOuR8eMmZxxgxQcJY4DA3NTcHP1jIxAj9MKTPURgHmN4/SxAn" + + "VFMLJL1JTIhSFqfLWJ8LSbK21Dt1XQEqgSdgdpfZruckCCiuqLBJm3fEni70NgO3wUxIhwmLH" + + "IYojK5ZSovZO7wfIUsxO55RJphL2szINQ5U1HcrJ8ul6tOf2uHYOr9P8jVWk7UdChOjijVOZf" + + "wcocoAxiJFFUbxU20u3QUib4hicnFXXpuTp2XnBpL6QTcnfFF7/QVCGqWy3y0iXebEmqyPduh" + + "kPb4HnBrSpzFJge/y7YkhhEMR+YfUvCp4Kqp+pToyjvH8p74gvOIo2Ry/wUeS/nXruvWh0rrg" + + "vW+sOkcel+eiPSF+a2ibB5B4aYttKdTSAr8cRUGSv9nl8VXM75HsLduRhnZB8VXYqX6dsLZ8j" + + "oyYj7LHQb9UrQn2XtjsnhWJJU1wO62ZWkqNX9HYP0tynU2ASJJD03mbLfp88WndsZe9LzQsnw" + + "PmKPIUo/wsTwjKm3duY8/y9qm5FhDfsXdSbK7LxEs23eS2F2aFzZNZkhOQqJHgcP6XuowOXJ3" + + "lVZ3jmQlSW23mybqpNUrgWoo2330RAdu9YdtTl2cfCrzy8mtwMOHm/xMTbttObbwTmtoasUgK" + + "i52mbs82OBOqmpAQPxGVkqZuzzYoCrSZkH1CRhIXxX95+ZHc1LJzYSvZXzzT7EvTSKDgG2D+F" + + "J0ZkOMz9wXT5+/1ZGZknRxxkXz5MRnnDZwzND3mHhVQ/JCfu1/IaWn2DUpcJtulMUXODNVlvB" + + "xe56uP1wPHwi4OMB35fVz1wWnVBxfCtNcI34QDrfnQvlyRL1BnZo/yKtwgoMTEVOwcIiqUWsA" + + "zmwjP/J6Mrh9Wx2fM4/lN4bRBlzIbAbc0lTrPitCV/qG6kdWw2pb7gp2xi9Kut7gh+kT4gF7g" + + "qSu2i3mQQP0mcwBzBQei0stR7+mT+v5X9v6Wl8Aqo7k1lqZ/uoAbm/GE7ZbUpqgByOe57MDXG" + + "k5sHfmNwyTXv6g+Z9V32JXdGMYwhjGMYQzLYNOm/wESzvR5z0LQ4QAAAABJRU5ErkJggg=="; + + protected static readonly new string Name = nameof(GitHubAssignedWidget); + + protected override string GetTitleIconData() + { + return TitleIconData; + } + + public override void RequestContentData() + { + var request = new SearchIssuesRequest() + { + Assignee = UserName, + }; + + RequestContentData(request); + } + + public override string GetTemplatePath(WidgetPageState page) + { + return page switch + { + WidgetPageState.SignIn => @"Widgets\Templates\GitHubSignInTemplate.json", + WidgetPageState.Configure => @"Widgets\Templates\GitHubAssignedConfigurationTemplate.json", + WidgetPageState.Content => @"Widgets\Templates\GitHubAssignedTemplate.json", + WidgetPageState.Loading => @"Widgets\Templates\GitHubLoadingTemplate.json", + _ => throw new NotImplementedException(), + }; + } +} diff --git a/src/GitHubExtension/Widgets/GitHubIssuesWidget.cs b/src/GitHubExtension/Widgets/GitHubIssuesWidget.cs index 0205a66e..95fc7dda 100644 --- a/src/GitHubExtension/Widgets/GitHubIssuesWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubIssuesWidget.cs @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text.Json.Nodes; using GitHubExtension.Client; using GitHubExtension.DataManager; using GitHubExtension.Helpers; using GitHubExtension.Widgets.Enums; -using Microsoft.Windows.Widgets.Providers; using Octokit; namespace GitHubExtension.Widgets; + internal class GitHubIssuesWidget : GitHubRepositoryWidget { private readonly string issuesIconData = IconLoader.GetIconAsBase64("issues.png"); diff --git a/src/GitHubExtension/Widgets/GitHubMentionedInWidget.cs b/src/GitHubExtension/Widgets/GitHubMentionedInWidget.cs index 524f1650..fbae0ed3 100644 --- a/src/GitHubExtension/Widgets/GitHubMentionedInWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubMentionedInWidget.cs @@ -1,66 +1,67 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using Octokit; - -namespace GitHubExtension.Widgets; -internal class GitHubMentionedInWidget : GitHubUserWidget -{ - private static readonly string TitleIconData = - "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAA" + - "lwSFlzAAAOwgAADsIBFShKgAAABTBJREFUeF7tW/tvFFUUrn+CGK1KtESJ0obsbKf7mJ3H" + - "zsw+uu2+t5s21JTWIn0ZQrGxRI1R8VGkCipoAiICSotVrEIp0FZQXv7I7k7/oOs5s9dH2r" + - "JMW2p2JvdLTtrcuTNzznfPOfdM7tkaBgYGS+BeCTzh7hejXF/1iqkf6ElVfjjwvKNtFc+0" + - "9igX06XgbIYEL1exXMkQ5VK6JIG+oHeWe1l4jJqxenjf1bYq08k+9VrWUOezxHZyLUuCM+" + - "mSfzzcQE2yDt9YaJvyS6qk3cgR7Y82ol1vw4cReSpJpIlEWSarUM7FifJzimg32oh+O2/q" + - "bpJwNMpR0x4Mfq+8WZ3NGNrvcPPVDBHPxgtNb6op94DYzPUKEddOQTeluwplp19HHfkRJS" + - "Eca+7EsNVvtRF1IWvgolITK0M63dqDLoQsQuwXuN0POan8j/Afjmz/O4S1hZzBv6Y8Qy+t" + - "jKa31OfAbRbVhRwmFKNxWN5ML9kWwtFoJ+YD/U6egBfk6fDKED6L7tDv5ok6lyXesXDlyT" + - "aBuz9Qi4uJOUE81XKP6/ZvopeWI3Ai1qXfhpi5mjFcPevYQqoMsEUOakCAfD5B+BE5QYeX" + - "I/A1EpDH5PcqHXIEkAAMAWkiTvi9UgsdXo7AyTIB4AGOJaDRCgGO9ACwixFglQBHhgDzAE" + - "YAI4ARwAhgBFgjgG2DjABGACOADjkCjABGACOAEcAIYAQwAhgBjIBKBLi6fI+IJ2Nn6cmQ" + - "8wi4kycyEFD5YMTJJ0NWQkA4Fu3EiXik3LRf2UKHbY/gbGYI7RK/ixfcg2ItHV4O73t6Tr" + - "8JBMxniXi6tYcO2xq+g6F6XFAkQPiyuZMOrwx3v1gb/DVdUhfMPhsDb6aXbImm/cEtCrUH" + - "CfAe0NvopfvDdyjcgMfj2vWceUweOBHz0Eu2gu/jcL0ynSqadoBHY88T1xe4v/v/F/L5xC" + - "68EQWbC6TJxK7A8ViX/0i0wzcebveNR9p9hyMd3g9CeW6dfQTYtuL/NNLh+2QdgvqACKAf" + - "6gnJrjd4CVbebPXJgfHpEjdQIfaXAvvrsMkIkoeB24f+Jwi4EDZOqb8BoxgiC/B3LkuwqY" + - "Letmp4Pwy9KP+YLKrz9Jn47HUI6od6mvreBX1hAbHPyb1Heoq+cnXwfhTaJk0kepULySIw" + - "SsxGSZQrGaKCYAcW/D9Ep1tG45D0pHiqtRsUXtTKXVwYbv8+f61yOUOUi2mC+qLe/s+jO7" + - "he4XH62rWDH5Xr0OUxiwpfNb8kfh8v4Iv0W1AzzKQH6TRLEL6IcsGZTAm9p+xJWQMU3o01" + - "CHrdmgV1wxAdC+X5UaWOvm5j4HlbzchTCdPdwDMG6HBF8K8H63BrBc8xE6x2E1Z+LmfA1v" + - "sCnWIfNO6RYtJk3Gw9s0KAB4xULqSKGDI6Gg4rL52LF/jR4Mau1EaB3ye1YsfVgwjguoVN" + - "4rct2HhZ3lYh2WF2xo8ubF+j0+wHKwT4DobrsU/3n1iHLK1Mp4sQPs/TKfYFPwwETAIBsN" + - "UsJQCLDUxKuNIY59iwjAUVdmy6uvyP0mn2Br9PjpsesCQJYlEDRVMBVxtjXYO/8g+Jovd9" + - "zX6JrhL4YdkMAVxh2H8Nzxtqyn8o3K5BVkejTZeHuMcKEj4/n6a3OQfYQS7BpyWWmdiIDH" + - "W2+XsCNN5MdnNZw38ksp1Odyag9m4AD1g0f1QBnmA2WUN5HPim5V7jiPIsneZsCMdjHvmn" + - "ZBE9QJ5KFrH0dFXqxnYisOz0HtBybqufmgwMDAwMDFWCmpq/ANfVYnfzsINAAAAAAElFTkSuQmCC"; - - protected static readonly new string Name = nameof(GitHubMentionedInWidget); - - protected override string GetTitleIconData() - { - return TitleIconData; - } - - public override void RequestContentData() - { - var request = new SearchIssuesRequest() - { - Mentions = UserName, - }; - - RequestContentData(request); - } - - public override string GetTemplatePath(WidgetPageState page) - { - return page switch - { - WidgetPageState.SignIn => @"Widgets\Templates\GitHubSignInTemplate.json", - WidgetPageState.Configure => @"Widgets\Templates\GitHubMentionedInConfigurationTemplate.json", - WidgetPageState.Content => @"Widgets\Templates\GitHubMentionedInTemplate.json", - WidgetPageState.Loading => @"Widgets\Templates\GitHubLoadingTemplate.json", - _ => throw new NotImplementedException(), - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Octokit; + +namespace GitHubExtension.Widgets; + +internal class GitHubMentionedInWidget : GitHubUserWidget +{ + private static readonly string TitleIconData = + "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAA" + + "lwSFlzAAAOwgAADsIBFShKgAAABTBJREFUeF7tW/tvFFUUrn+CGK1KtESJ0obsbKf7mJ3H" + + "zsw+uu2+t5s21JTWIn0ZQrGxRI1R8VGkCipoAiICSotVrEIp0FZQXv7I7k7/oOs5s9dH2r" + + "JMW2p2JvdLTtrcuTNzznfPOfdM7tkaBgYGS+BeCTzh7hejXF/1iqkf6ElVfjjwvKNtFc+0" + + "9igX06XgbIYEL1exXMkQ5VK6JIG+oHeWe1l4jJqxenjf1bYq08k+9VrWUOezxHZyLUuCM+" + + "mSfzzcQE2yDt9YaJvyS6qk3cgR7Y82ol1vw4cReSpJpIlEWSarUM7FifJzimg32oh+O2/q" + + "bpJwNMpR0x4Mfq+8WZ3NGNrvcPPVDBHPxgtNb6op94DYzPUKEddOQTeluwplp19HHfkRJS" + + "Eca+7EsNVvtRF1IWvgolITK0M63dqDLoQsQuwXuN0POan8j/Afjmz/O4S1hZzBv6Y8Qy+t" + + "jKa31OfAbRbVhRwmFKNxWN5ML9kWwtFoJ+YD/U6egBfk6fDKED6L7tDv5ok6lyXesXDlyT" + + "aBuz9Qi4uJOUE81XKP6/ZvopeWI3Ai1qXfhpi5mjFcPevYQqoMsEUOakCAfD5B+BE5QYeX" + + "I/A1EpDH5PcqHXIEkAAMAWkiTvi9UgsdXo7AyTIB4AGOJaDRCgGO9ACwixFglQBHhgDzAE" + + "YAI4ARwAhgBFgjgG2DjABGACOADjkCjABGACOAEcAIYAQwAhgBjIBKBLi6fI+IJ2Nn6cmQ" + + "8wi4kycyEFD5YMTJJ0NWQkA4Fu3EiXik3LRf2UKHbY/gbGYI7RK/ixfcg2ItHV4O73t6Tr" + + "8JBMxniXi6tYcO2xq+g6F6XFAkQPiyuZMOrwx3v1gb/DVdUhfMPhsDb6aXbImm/cEtCrUH" + + "CfAe0NvopfvDdyjcgMfj2vWceUweOBHz0Eu2gu/jcL0ynSqadoBHY88T1xe4v/v/F/L5xC" + + "68EQWbC6TJxK7A8ViX/0i0wzcebveNR9p9hyMd3g9CeW6dfQTYtuL/NNLh+2QdgvqACKAf" + + "6gnJrjd4CVbebPXJgfHpEjdQIfaXAvvrsMkIkoeB24f+Jwi4EDZOqb8BoxgiC/B3LkuwqY" + + "Letmp4Pwy9KP+YLKrz9Jn47HUI6od6mvreBX1hAbHPyb1Heoq+cnXwfhTaJk0kepULySIw" + + "SsxGSZQrGaKCYAcW/D9Ep1tG45D0pHiqtRsUXtTKXVwYbv8+f61yOUOUi2mC+qLe/s+jO7" + + "he4XH62rWDH5Xr0OUxiwpfNb8kfh8v4Iv0W1AzzKQH6TRLEL6IcsGZTAm9p+xJWQMU3o01" + + "CHrdmgV1wxAdC+X5UaWOvm5j4HlbzchTCdPdwDMG6HBF8K8H63BrBc8xE6x2E1Z+LmfA1v" + + "sCnWIfNO6RYtJk3Gw9s0KAB4xULqSKGDI6Gg4rL52LF/jR4Mau1EaB3ye1YsfVgwjguoVN" + + "4rct2HhZ3lYh2WF2xo8ubF+j0+wHKwT4DobrsU/3n1iHLK1Mp4sQPs/TKfYFPwwETAIBsN" + + "UsJQCLDUxKuNIY59iwjAUVdmy6uvyP0mn2Br9PjpsesCQJYlEDRVMBVxtjXYO/8g+Jovd9" + + "zX6JrhL4YdkMAVxh2H8Nzxtqyn8o3K5BVkejTZeHuMcKEj4/n6a3OQfYQS7BpyWWmdiIDH" + + "W2+XsCNN5MdnNZw38ksp1Odyag9m4AD1g0f1QBnmA2WUN5HPim5V7jiPIsneZsCMdjHvmn" + + "ZBE9QJ5KFrH0dFXqxnYisOz0HtBybqufmgwMDAwMDFWCmpq/ANfVYnfzsINAAAAAAElFTkSuQmCC"; + + protected static readonly new string Name = nameof(GitHubMentionedInWidget); + + protected override string GetTitleIconData() + { + return TitleIconData; + } + + public override void RequestContentData() + { + var request = new SearchIssuesRequest() + { + Mentions = UserName, + }; + + RequestContentData(request); + } + + public override string GetTemplatePath(WidgetPageState page) + { + return page switch + { + WidgetPageState.SignIn => @"Widgets\Templates\GitHubSignInTemplate.json", + WidgetPageState.Configure => @"Widgets\Templates\GitHubMentionedInConfigurationTemplate.json", + WidgetPageState.Content => @"Widgets\Templates\GitHubMentionedInTemplate.json", + WidgetPageState.Loading => @"Widgets\Templates\GitHubLoadingTemplate.json", + _ => throw new NotImplementedException(), + }; + } +} diff --git a/src/GitHubExtension/Widgets/GitHubPullsWidget.cs b/src/GitHubExtension/Widgets/GitHubPullsWidget.cs index 318c4250..df5362d1 100644 --- a/src/GitHubExtension/Widgets/GitHubPullsWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubPullsWidget.cs @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text.Json.Nodes; using GitHubExtension.Client; using GitHubExtension.DataManager; using GitHubExtension.Helpers; using GitHubExtension.Widgets.Enums; -using Microsoft.Windows.Widgets.Providers; using Octokit; namespace GitHubExtension.Widgets; + internal class GitHubPullsWidget : GitHubRepositoryWidget { private readonly string pullsIconData = IconLoader.GetIconAsBase64("pulls.png"); diff --git a/src/GitHubExtension/Widgets/GitHubRepositoryWidget.cs b/src/GitHubExtension/Widgets/GitHubRepositoryWidget.cs index 09b9501a..c8a10514 100644 --- a/src/GitHubExtension/Widgets/GitHubRepositoryWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubRepositoryWidget.cs @@ -1,15 +1,12 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using GitHubExtension.Client; using GitHubExtension.DataManager; -using GitHubExtension.DeveloperId; using GitHubExtension.Helpers; using GitHubExtension.Widgets.Enums; -using Microsoft.Windows.DevHome.SDK; using Microsoft.Windows.Widgets.Providers; namespace GitHubExtension.Widgets; diff --git a/src/GitHubExtension/Widgets/GitHubReviewWidget.cs b/src/GitHubExtension/Widgets/GitHubReviewWidget.cs index 03907b95..9a248426 100644 --- a/src/GitHubExtension/Widgets/GitHubReviewWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubReviewWidget.cs @@ -1,81 +1,82 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System.Text.Json.Nodes; -using GitHubExtension.DataManager; -using GitHubExtension.Helpers; -using Microsoft.Windows.Widgets.Providers; -using Octokit; - -namespace GitHubExtension.Widgets; -internal class GitHubReviewWidget : GitHubUserWidget -{ - protected static readonly new string Name = nameof(GitHubReviewWidget); - - public GitHubReviewWidget() - : base() - { - // This widget doest not allow customization, so this value will not change. - ShowCategory = SearchCategory.PullRequests; - } - - public override void RequestContentData() - { - SearchIssuesRequest request = new SearchIssuesRequest($"review-requested:{UserName}"); - - RequestContentData(request); - } - - protected override string GetTitleIconData() - { - return IconLoader.GetIconAsBase64("pulls.png"); - } - - // This widget does not have "ShowCategory" as a variable. - // So we override this method to not care about this data. - protected override void ResetWidgetInfoFromState() - { - base.ResetWidgetInfoFromState(); - ShowCategory = SearchCategory.PullRequests; - } - - // Overriding this method because this widget only cares about the account. - public override void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs) - { - if (actionInvokedArgs.Verb == "Submit") - { - var data = actionInvokedArgs.Data; - var dataObject = JsonNode.Parse(data); - - if (dataObject == null) - { - return; - } - - DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; - - ConfigurationData = data; - - // If we got here during the customization flow, we need to LoadContentData again - // so we can show the loading page rather than stale data. - LoadContentData(); - UpdateActivityState(); - } - else - { - base.OnActionInvoked(actionInvokedArgs); - } - } - - public override string GetTemplatePath(WidgetPageState page) - { - return page switch - { - WidgetPageState.SignIn => @"Widgets\Templates\GitHubSignInTemplate.json", - WidgetPageState.Configure => @"Widgets\Templates\GitHubReviewConfigurationTemplate.json", - WidgetPageState.Content => @"Widgets\Templates\GitHubReviewTemplate.json", - WidgetPageState.Loading => @"Widgets\Templates\GitHubLoadingTemplate.json", - _ => throw new NotImplementedException(), - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Nodes; +using GitHubExtension.DataManager; +using GitHubExtension.Helpers; +using Microsoft.Windows.Widgets.Providers; +using Octokit; + +namespace GitHubExtension.Widgets; + +internal class GitHubReviewWidget : GitHubUserWidget +{ + protected static readonly new string Name = nameof(GitHubReviewWidget); + + public GitHubReviewWidget() + : base() + { + // This widget does not allow customization, so this value will not change. + ShowCategory = SearchCategory.PullRequests; + } + + public override void RequestContentData() + { + SearchIssuesRequest request = new SearchIssuesRequest($"review-requested:{UserName}"); + + RequestContentData(request); + } + + protected override string GetTitleIconData() + { + return IconLoader.GetIconAsBase64("pulls.png"); + } + + // This widget does not have "ShowCategory" as a variable. + // So we override this method to not care about this data. + protected override void ResetWidgetInfoFromState() + { + base.ResetWidgetInfoFromState(); + ShowCategory = SearchCategory.PullRequests; + } + + // Overriding this method because this widget only cares about the account. + public override void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs) + { + if (actionInvokedArgs.Verb == "Submit") + { + var data = actionInvokedArgs.Data; + var dataObject = JsonNode.Parse(data); + + if (dataObject == null) + { + return; + } + + DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; + + ConfigurationData = data; + + // If we got here during the customization flow, we need to LoadContentData again + // so we can show the loading page rather than stale data. + LoadContentData(); + UpdateActivityState(); + } + else + { + base.OnActionInvoked(actionInvokedArgs); + } + } + + public override string GetTemplatePath(WidgetPageState page) + { + return page switch + { + WidgetPageState.SignIn => @"Widgets\Templates\GitHubSignInTemplate.json", + WidgetPageState.Configure => @"Widgets\Templates\GitHubReviewConfigurationTemplate.json", + WidgetPageState.Content => @"Widgets\Templates\GitHubReviewTemplate.json", + WidgetPageState.Loading => @"Widgets\Templates\GitHubLoadingTemplate.json", + _ => throw new NotImplementedException(), + }; + } +} diff --git a/src/GitHubExtension/Widgets/GitHubUserWidget.cs b/src/GitHubExtension/Widgets/GitHubUserWidget.cs index d3487618..38796c4f 100644 --- a/src/GitHubExtension/Widgets/GitHubUserWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubUserWidget.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text.Json; using System.Text.Json.Nodes; @@ -12,6 +12,7 @@ using Octokit; namespace GitHubExtension.Widgets; + internal abstract class GitHubUserWidget : GitHubWidget { protected static readonly new string Name = nameof(GitHubUserWidget); @@ -318,11 +319,11 @@ public override string GetData(WidgetPageState page) protected IDeveloperId? GetWidgetDeveloperId() { - foreach (var devid in DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds) + foreach (var devId in DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds) { - if (devid.LoginId == DeveloperLoginId) + if (devId.LoginId == DeveloperLoginId) { - return devid; + return devId; } } diff --git a/src/GitHubExtension/Widgets/GitHubWidget.cs b/src/GitHubExtension/Widgets/GitHubWidget.cs index 8d89ff4a..dc208403 100644 --- a/src/GitHubExtension/Widgets/GitHubWidget.cs +++ b/src/GitHubExtension/Widgets/GitHubWidget.cs @@ -1,9 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text; using System.Text.Json.Nodes; -using GitHubExtension.Client; using GitHubExtension.DataManager; using GitHubExtension.DeveloperId; using GitHubExtension.Helpers; diff --git a/src/GitHubExtension/Widgets/Guids.cs b/src/GitHubExtension/Widgets/Guids.cs index 78b08ee0..7a4a54d1 100644 --- a/src/GitHubExtension/Widgets/Guids.cs +++ b/src/GitHubExtension/Widgets/Guids.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.InteropServices; diff --git a/src/GitHubExtension/Widgets/IWidgetImplFactory.cs b/src/GitHubExtension/Widgets/IWidgetImplFactory.cs index b1aa02cb..5d07a78f 100644 --- a/src/GitHubExtension/Widgets/IWidgetImplFactory.cs +++ b/src/GitHubExtension/Widgets/IWidgetImplFactory.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.Widgets.Providers; namespace GitHubExtension.Widgets; + internal interface IWidgetImplFactory { public WidgetImpl Create(WidgetContext widgetContext, string state); diff --git a/src/GitHubExtension/Widgets/Logging.cs b/src/GitHubExtension/Widgets/Logging.cs index df2bea82..60ae63af 100644 --- a/src/GitHubExtension/Widgets/Logging.cs +++ b/src/GitHubExtension/Widgets/Logging.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Windows.Storage; namespace GitHubExtension.Widgets; + public class Log { private static Logger? _logger; diff --git a/src/GitHubExtension/Widgets/Templates/GitHubAssignedConfigurationTemplate.json b/src/GitHubExtension/Widgets/Templates/GitHubAssignedConfigurationTemplate.json index d55ebae2..dc270763 100644 --- a/src/GitHubExtension/Widgets/Templates/GitHubAssignedConfigurationTemplate.json +++ b/src/GitHubExtension/Widgets/Templates/GitHubAssignedConfigurationTemplate.json @@ -1,69 +1,69 @@ -{ - "type": "AdaptiveCard", - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.5", - "body": [ - { - "type": "Input.ChoiceSet", - "id": "account", - "placeholder": "%Widget_Template/ChooseAccountPlaceholder%", - "value": "${selectedDevId}", - "choices": [ - { - "$data": "${accounts}", - "title": "${devid}", - "value": "${devid}" - } - ] - }, - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "Input.ChoiceSet", - "id": "showCategory", - "style": "compact", - "isMultiSelect": false, - "value": "${showCategory}", - "choices": [ - { - "title": "%Widget_Template/Issues%", - "value": "Issues" - }, - { - "title": "%Widget_Template/PullRequests%", - "value": "PullRequests" - }, - { - "title": "%Widget_Template/IssuesAndPullRequests%", - "value": "IssuesAndPullRequests" - } - ] - } - ] - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "ActionSet", - "actions": [ - { - "type": "Action.Execute", - "title": "%Widget_Template_Button/Submit%", - "verb": "Submit", - "associatedInputs": "auto" - } - ] - } - ] - } - ] - } - ] +{ + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5", + "body": [ + { + "type": "Input.ChoiceSet", + "id": "account", + "placeholder": "%Widget_Template/ChooseAccountPlaceholder%", + "value": "${selectedDevId}", + "choices": [ + { + "$data": "${accounts}", + "title": "${devid}", + "value": "${devid}" + } + ] + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "Input.ChoiceSet", + "id": "showCategory", + "style": "compact", + "isMultiSelect": false, + "value": "${showCategory}", + "choices": [ + { + "title": "%Widget_Template/Issues%", + "value": "Issues" + }, + { + "title": "%Widget_Template/PullRequests%", + "value": "PullRequests" + }, + { + "title": "%Widget_Template/IssuesAndPullRequests%", + "value": "IssuesAndPullRequests" + } + ] + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "ActionSet", + "actions": [ + { + "type": "Action.Execute", + "title": "%Widget_Template_Button/Submit%", + "verb": "Submit", + "associatedInputs": "auto" + } + ] + } + ] + } + ] + } + ] } \ No newline at end of file diff --git a/src/GitHubExtension/Widgets/Templates/GitHubAssignedTemplate.json b/src/GitHubExtension/Widgets/Templates/GitHubAssignedTemplate.json index 2a60c103..e2835d97 100644 --- a/src/GitHubExtension/Widgets/Templates/GitHubAssignedTemplate.json +++ b/src/GitHubExtension/Widgets/Templates/GitHubAssignedTemplate.json @@ -1,152 +1,152 @@ -{ - "type": "AdaptiveCard", - "body": [ - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "Image", - "url": "data:image/png;base64,${titleIconUrl}", - "size": "large" - } - ], - "verticalContentAlignment": "Center" - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "text": "${userName}", - "size": "Large", - "style": "Heading", - "wrap": true - } - ], - "verticalContentAlignment": "Center" - } - ] - }, - { - "type": "Container", - "$when": "${(count(items) == 0)}", - "items": [ - { - "type": "TextBlock", - "text": "${if(is_loading_data, '%Widget_Template/ContentLoading%', '%Widget_Template/EmptyAssigned%')}", - "wrap": true, - "weight": "bolder", - "horizontalAlignment": "center" - } - ], - "spacing": "medium", - "verticalContentAlignment": "center" - }, - { - "$data": "${items}", - "type": "ColumnSet", - "style": "emphasis", - "spacing": "default", - "selectAction": { - "type": "Action.OpenUrl", - "url": "${url}", - "tooltip": "%Widget_Template_Tooltip/OpenIssue%" - }, - "columns": [ - { - "type": "Column", - "items": [ - { - "type": "Image", - "url": "data:image/png;base64,${iconUrl}", - "size": "medium" - } - ], - "width": "12px" - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "size": "medium", - "weight": "bolder", - "text": "${title}", - "wrap": true, - "maxLines": 2 - }, - { - "type": "ColumnSet", - "spacing": "None", - "wrap": true, - "columns": [ - { - "type": "Column", - "width": "12px", - "items": [ - { - "type": "Image", - "style": "Person", - "url": "${avatar}", - "size": "small" - } - ] - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "text": "${user}", - "isSubtle": true, - "size": "small", - "spacing": "None", - "weight": "bolder" - } - ] - } - ] - }, - { - "type": "TextBlock", - "size": "small", - "spacing": "small", - "text": "#${number} %Widget_Template/Updated% ${date}", - "isSubtle": true, - "wrap": true, - "maxLines": 2 - }, - { - "type": "TextBlock", - "size": "small", - "spacing": "small", - "text": "${repo}", - "isSubtle": true, - "wrap": true, - "maxLines": 2 - }, - { - "type": "LabelGroup", - "labels": [ - { - "$data": "${labels}", - "text": "${name}", - "color": "${color}" - } - ] - } - ] - } - ] - } - ], - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.5" -} +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${titleIconUrl}", + "size": "large" + } + ], + "verticalContentAlignment": "Center" + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "${userName}", + "size": "Large", + "style": "Heading", + "wrap": true + } + ], + "verticalContentAlignment": "Center" + } + ] + }, + { + "type": "Container", + "$when": "${(count(items) == 0)}", + "items": [ + { + "type": "TextBlock", + "text": "${if(is_loading_data, '%Widget_Template/ContentLoading%', '%Widget_Template/EmptyAssigned%')}", + "wrap": true, + "weight": "bolder", + "horizontalAlignment": "center" + } + ], + "spacing": "medium", + "verticalContentAlignment": "center" + }, + { + "$data": "${items}", + "type": "ColumnSet", + "style": "emphasis", + "spacing": "default", + "selectAction": { + "type": "Action.OpenUrl", + "url": "${url}", + "tooltip": "%Widget_Template_Tooltip/OpenIssue%" + }, + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${iconUrl}", + "size": "medium" + } + ], + "width": "12px" + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "size": "medium", + "weight": "bolder", + "text": "${title}", + "wrap": true, + "maxLines": 2 + }, + { + "type": "ColumnSet", + "spacing": "None", + "wrap": true, + "columns": [ + { + "type": "Column", + "width": "12px", + "items": [ + { + "type": "Image", + "style": "Person", + "url": "${avatar}", + "size": "small" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "${user}", + "isSubtle": true, + "size": "small", + "spacing": "None", + "weight": "bolder" + } + ] + } + ] + }, + { + "type": "TextBlock", + "size": "small", + "spacing": "small", + "text": "#${number} %Widget_Template/Updated% ${date}", + "isSubtle": true, + "wrap": true, + "maxLines": 2 + }, + { + "type": "TextBlock", + "size": "small", + "spacing": "small", + "text": "${repo}", + "isSubtle": true, + "wrap": true, + "maxLines": 2 + }, + { + "type": "LabelGroup", + "labels": [ + { + "$data": "${labels}", + "text": "${name}", + "color": "${color}" + } + ] + } + ] + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5" +} diff --git a/src/GitHubExtension/Widgets/Templates/GitHubMentionedInConfigurationTemplate.json b/src/GitHubExtension/Widgets/Templates/GitHubMentionedInConfigurationTemplate.json index bd7d646c..70becf0b 100644 --- a/src/GitHubExtension/Widgets/Templates/GitHubMentionedInConfigurationTemplate.json +++ b/src/GitHubExtension/Widgets/Templates/GitHubMentionedInConfigurationTemplate.json @@ -1,68 +1,68 @@ -{ - "type": "AdaptiveCard", - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.5", - "body": [ - { - "type": "Input.ChoiceSet", - "id": "account", - "placeholder": "%Widget_Template/ChooseAccountPlaceholder%", - "value": "${selectedDevId}", - "choices": [ - { - "$data": "${accounts}", - "title": "${devid}", - "value": "${devid}" - } - ] - }, - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "Input.ChoiceSet", - "id": "showCategory", - "isMultiSelect": false, - "value": "${showCategory}", - "choices": [ - { - "title": "%Widget_Template/Issues%", - "value": "Issues" - }, - { - "title": "%Widget_Template/PullRequests%", - "value": "PullRequests" - }, - { - "title": "%Widget_Template/IssuesAndPullRequests%", - "value": "IssuesAndPullRequests" - } - ] - } - ] - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "ActionSet", - "actions": [ - { - "type": "Action.Execute", - "title": "%Widget_Template_Button/Submit%", - "verb": "Submit", - "associatedInputs": "auto" - } - ] - } - ] - } - ] - } - ] +{ + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5", + "body": [ + { + "type": "Input.ChoiceSet", + "id": "account", + "placeholder": "%Widget_Template/ChooseAccountPlaceholder%", + "value": "${selectedDevId}", + "choices": [ + { + "$data": "${accounts}", + "title": "${devid}", + "value": "${devid}" + } + ] + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "Input.ChoiceSet", + "id": "showCategory", + "isMultiSelect": false, + "value": "${showCategory}", + "choices": [ + { + "title": "%Widget_Template/Issues%", + "value": "Issues" + }, + { + "title": "%Widget_Template/PullRequests%", + "value": "PullRequests" + }, + { + "title": "%Widget_Template/IssuesAndPullRequests%", + "value": "IssuesAndPullRequests" + } + ] + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "ActionSet", + "actions": [ + { + "type": "Action.Execute", + "title": "%Widget_Template_Button/Submit%", + "verb": "Submit", + "associatedInputs": "auto" + } + ] + } + ] + } + ] + } + ] } \ No newline at end of file diff --git a/src/GitHubExtension/Widgets/Templates/GitHubMentionedInTemplate.json b/src/GitHubExtension/Widgets/Templates/GitHubMentionedInTemplate.json index 23f60725..ee19feea 100644 --- a/src/GitHubExtension/Widgets/Templates/GitHubMentionedInTemplate.json +++ b/src/GitHubExtension/Widgets/Templates/GitHubMentionedInTemplate.json @@ -1,153 +1,153 @@ -{ - "type": "AdaptiveCard", - "body": [ - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "Image", - "url": "data:image/png;base64,${titleIconUrl}", - "size": "large" - } - ], - "verticalContentAlignment": "Center" - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "text": "${userName}", - "size": "Large", - "style": "Heading", - "wrap": true - } - ], - "verticalContentAlignment": "Center" - } - ] - }, - { - "type": "Container", - "$when": "${(count(items) == 0)}", - "items": [ - { - "type": "TextBlock", - "text": "${if(is_loading_data, '%Widget_Template/ContentLoading%', '%Widget_Template/EmptyMentioned%')}", - "wrap": true, - "weight": "bolder", - "horizontalAlignment": "center" - } - ], - "spacing": "medium", - "verticalContentAlignment": "center" - }, - { - "$data": "${items}", - "type": "ColumnSet", - "style": "emphasis", - "spacing": "default", - "selectAction": { - "type": "Action.OpenUrl", - "url": "${url}", - "tooltip": "%Widget_Template_Tooltip/OpenIssue%" - }, - "columns": [ - { - "type": "Column", - "items": [ - { - "type": "Image", - "url": "data:image/png;base64,${iconUrl}", - "size": "medium" - } - ], - "width": "12px" - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "size": "medium", - "wrap": true, - "maxLines": 2, - "weight": "Bolder", - "text": "${title}" - }, - { - "type": "ColumnSet", - "spacing": "None", - "wrap": true, - "columns": [ - { - "type": "Column", - "width": "12px", - "items": [ - { - "type": "Image", - "style": "Person", - "url": "${avatar}", - "size": "small" - } - ] - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "text": "${user}", - "isSubtle": true, - "size": "small", - "spacing": "None", - "weight": "bolder" - } - ] - } - ] - }, - { - "type": "TextBlock", - "size": "small", - "spacing": "small", - "spacing": "None", - "text": "#${number} %Widget_Template/Updated% ${date}", - "isSubtle": true, - "wrap": true, - "maxLines": 2 - }, - { - "type": "TextBlock", - "size": "small", - "spacing": "small", - "text": "${repo}", - "isSubtle": true, - "wrap": true, - "maxLines": 2 - }, - { - "type": "LabelGroup", - "labels": [ - { - "$data": "${labels}", - "text": "${name}", - "color": "${color}" - } - ] - } - ] - } - ] - } - ], - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.5" -} +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${titleIconUrl}", + "size": "large" + } + ], + "verticalContentAlignment": "Center" + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "${userName}", + "size": "Large", + "style": "Heading", + "wrap": true + } + ], + "verticalContentAlignment": "Center" + } + ] + }, + { + "type": "Container", + "$when": "${(count(items) == 0)}", + "items": [ + { + "type": "TextBlock", + "text": "${if(is_loading_data, '%Widget_Template/ContentLoading%', '%Widget_Template/EmptyMentioned%')}", + "wrap": true, + "weight": "bolder", + "horizontalAlignment": "center" + } + ], + "spacing": "medium", + "verticalContentAlignment": "center" + }, + { + "$data": "${items}", + "type": "ColumnSet", + "style": "emphasis", + "spacing": "default", + "selectAction": { + "type": "Action.OpenUrl", + "url": "${url}", + "tooltip": "%Widget_Template_Tooltip/OpenIssue%" + }, + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${iconUrl}", + "size": "medium" + } + ], + "width": "12px" + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "size": "medium", + "wrap": true, + "maxLines": 2, + "weight": "Bolder", + "text": "${title}" + }, + { + "type": "ColumnSet", + "spacing": "None", + "wrap": true, + "columns": [ + { + "type": "Column", + "width": "12px", + "items": [ + { + "type": "Image", + "style": "Person", + "url": "${avatar}", + "size": "small" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "${user}", + "isSubtle": true, + "size": "small", + "spacing": "None", + "weight": "bolder" + } + ] + } + ] + }, + { + "type": "TextBlock", + "size": "small", + "spacing": "small", + "spacing": "None", + "text": "#${number} %Widget_Template/Updated% ${date}", + "isSubtle": true, + "wrap": true, + "maxLines": 2 + }, + { + "type": "TextBlock", + "size": "small", + "spacing": "small", + "text": "${repo}", + "isSubtle": true, + "wrap": true, + "maxLines": 2 + }, + { + "type": "LabelGroup", + "labels": [ + { + "$data": "${labels}", + "text": "${name}", + "color": "${color}" + } + ] + } + ] + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5" +} diff --git a/src/GitHubExtension/Widgets/Templates/GitHubReviewTemplate.json b/src/GitHubExtension/Widgets/Templates/GitHubReviewTemplate.json index 936c9506..cc709001 100644 --- a/src/GitHubExtension/Widgets/Templates/GitHubReviewTemplate.json +++ b/src/GitHubExtension/Widgets/Templates/GitHubReviewTemplate.json @@ -1,152 +1,152 @@ -{ - "type": "AdaptiveCard", - "body": [ - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "Image", - "url": "data:image/png;base64,${titleIconUrl}", - "size": "large" - } - ], - "verticalContentAlignment": "Center" - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "text": "${userName}", - "size": "Large", - "style": "Heading", - "wrap": true - } - ], - "verticalContentAlignment": "Center" - } - ] - }, - { - "type": "Container", - "$when": "${(count(items) == 0)}", - "items": [ - { - "type": "TextBlock", - "text": "${if(is_loading_data, '%Widget_Template/ContentLoading%', '%Widget_Template/EmptyReviews%')}", - "wrap": true, - "weight": "bolder", - "horizontalAlignment": "center" - } - ], - "spacing": "medium", - "verticalContentAlignment": "center" - }, - { - "$data": "${items}", - "type": "ColumnSet", - "style": "emphasis", - "spacing": "default", - "selectAction": { - "type": "Action.OpenUrl", - "url": "${url}", - "tooltip": "%Widget_Template_Tooltip/OpenIssue%" - }, - "columns": [ - { - "type": "Column", - "items": [ - { - "type": "Image", - "url": "data:image/png;base64,${iconUrl}", - "size": "medium" - } - ], - "width": "12px" - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "size": "medium", - "weight": "bolder", - "text": "${title}", - "wrap": true, - "maxLines": 2 - }, - { - "type": "ColumnSet", - "spacing": "None", - "wrap": true, - "columns": [ - { - "type": "Column", - "width": "12px", - "items": [ - { - "type": "Image", - "style": "Person", - "url": "${avatar}", - "size": "small" - } - ] - }, - { - "type": "Column", - "width": "stretch", - "items": [ - { - "type": "TextBlock", - "text": "${user}", - "isSubtle": true, - "size": "small", - "spacing": "None", - "weight": "bolder" - } - ] - } - ] - }, - { - "type": "TextBlock", - "size": "small", - "spacing": "small", - "text": "#${number} %Widget_Template/Updated% ${date}", - "isSubtle": true, - "wrap": true, - "maxLines": 2 - }, - { - "type": "TextBlock", - "size": "small", - "spacing": "small", - "text": "${repo}", - "isSubtle": true, - "wrap": true, - "maxLines": 2 - }, - { - "type": "LabelGroup", - "labels": [ - { - "$data": "${labels}", - "text": "${name}", - "color": "${color}" - } - ] - } - ] - } - ] - } - ], - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.5" +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${titleIconUrl}", + "size": "large" + } + ], + "verticalContentAlignment": "Center" + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "${userName}", + "size": "Large", + "style": "Heading", + "wrap": true + } + ], + "verticalContentAlignment": "Center" + } + ] + }, + { + "type": "Container", + "$when": "${(count(items) == 0)}", + "items": [ + { + "type": "TextBlock", + "text": "${if(is_loading_data, '%Widget_Template/ContentLoading%', '%Widget_Template/EmptyReviews%')}", + "wrap": true, + "weight": "bolder", + "horizontalAlignment": "center" + } + ], + "spacing": "medium", + "verticalContentAlignment": "center" + }, + { + "$data": "${items}", + "type": "ColumnSet", + "style": "emphasis", + "spacing": "default", + "selectAction": { + "type": "Action.OpenUrl", + "url": "${url}", + "tooltip": "%Widget_Template_Tooltip/OpenIssue%" + }, + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${iconUrl}", + "size": "medium" + } + ], + "width": "12px" + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "size": "medium", + "weight": "bolder", + "text": "${title}", + "wrap": true, + "maxLines": 2 + }, + { + "type": "ColumnSet", + "spacing": "None", + "wrap": true, + "columns": [ + { + "type": "Column", + "width": "12px", + "items": [ + { + "type": "Image", + "style": "Person", + "url": "${avatar}", + "size": "small" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "${user}", + "isSubtle": true, + "size": "small", + "spacing": "None", + "weight": "bolder" + } + ] + } + ] + }, + { + "type": "TextBlock", + "size": "small", + "spacing": "small", + "text": "#${number} %Widget_Template/Updated% ${date}", + "isSubtle": true, + "wrap": true, + "maxLines": 2 + }, + { + "type": "TextBlock", + "size": "small", + "spacing": "small", + "text": "${repo}", + "isSubtle": true, + "wrap": true, + "maxLines": 2 + }, + { + "type": "LabelGroup", + "labels": [ + { + "$data": "${labels}", + "text": "${name}", + "color": "${color}" + } + ] + } + ] + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5" } \ No newline at end of file diff --git a/src/GitHubExtension/Widgets/WidgetImpl.cs b/src/GitHubExtension/Widgets/WidgetImpl.cs index f25454ce..c6ee2c8b 100644 --- a/src/GitHubExtension/Widgets/WidgetImpl.cs +++ b/src/GitHubExtension/Widgets/WidgetImpl.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.Telemetry; using Microsoft.Windows.Widgets.Providers; namespace GitHubExtension.Widgets; + public abstract class WidgetImpl { private readonly ILogger logger; diff --git a/src/GitHubExtension/Widgets/WidgetImplFactory.cs b/src/GitHubExtension/Widgets/WidgetImplFactory.cs index 1090d059..b4ead9f3 100644 --- a/src/GitHubExtension/Widgets/WidgetImplFactory.cs +++ b/src/GitHubExtension/Widgets/WidgetImplFactory.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.Widgets.Providers; namespace GitHubExtension.Widgets; + internal class WidgetImplFactory : IWidgetImplFactory where T : WidgetImpl, new() { diff --git a/src/GitHubExtension/Widgets/WidgetProvider.cs b/src/GitHubExtension/Widgets/WidgetProvider.cs index 40066a57..74dce358 100644 --- a/src/GitHubExtension/Widgets/WidgetProvider.cs +++ b/src/GitHubExtension/Widgets/WidgetProvider.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.InteropServices; using Microsoft.Windows.Widgets.Providers; diff --git a/src/GitHubExtension/Widgets/WidgetProviderFactory.cs b/src/GitHubExtension/Widgets/WidgetProviderFactory.cs index 197a07c2..a72d36d6 100644 --- a/src/GitHubExtension/Widgets/WidgetProviderFactory.cs +++ b/src/GitHubExtension/Widgets/WidgetProviderFactory.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.InteropServices; using GitHubExtension.Widgets.COM; diff --git a/src/GitHubExtension/Widgets/WidgetServer.cs b/src/GitHubExtension/Widgets/WidgetServer.cs index fb2e3437..28a526c7 100644 --- a/src/GitHubExtension/Widgets/WidgetServer.cs +++ b/src/GitHubExtension/Widgets/WidgetServer.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -7,6 +7,7 @@ using Microsoft.Windows.Widgets.Providers; namespace GitHubExtension.Widgets; + public sealed class WidgetServer : IDisposable { private readonly HashSet registrationCookies = new (); diff --git a/src/GitHubExtensionServer/GitHubExtensionServer.csproj b/src/GitHubExtensionServer/GitHubExtensionServer.csproj index bdf488cb..5eb4554b 100644 --- a/src/GitHubExtensionServer/GitHubExtensionServer.csproj +++ b/src/GitHubExtensionServer/GitHubExtensionServer.csproj @@ -26,6 +26,7 @@ false true Dev + portable @@ -37,17 +38,6 @@ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - @@ -58,28 +48,4 @@ $(DefineConstants);CANARY_BUILD $(DefineConstants);STABLE_BUILD - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - diff --git a/src/GitHubExtensionServer/Logging.cs b/src/GitHubExtensionServer/Logging.cs index 206e05f5..f0a12cfd 100644 --- a/src/GitHubExtensionServer/Logging.cs +++ b/src/GitHubExtensionServer/Logging.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using Windows.Storage; namespace GitHubExtension.ExtensionServer; + public class Log { private static Logger? _logger; diff --git a/src/GitHubExtensionServer/Program.cs b/src/GitHubExtensionServer/Program.cs index 0e32e29b..17b0a190 100644 --- a/src/GitHubExtensionServer/Program.cs +++ b/src/GitHubExtensionServer/Program.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.ExtensionServer; using Microsoft.Windows.AppLifecycle; @@ -8,6 +8,7 @@ using Windows.Management.Deployment; namespace GitHubExtension; + public sealed class Program { [MTAThread] @@ -126,7 +127,7 @@ private static void HandleCOMServerActivation() using var dataUpdater = new DataManager.DataUpdater(GitHubDataManager.Update); _ = dataUpdater.Start(); - // This will make the main thread wait until the event is signalled by the extension class. + // This will make the main thread wait until the event is signaled by the extension class. // Since we have single instance of the extension object, we exit as soon as it is disposed. extensionDisposedEvent.WaitOne(); Log.Logger()?.ReportInfo($"Extension is disposed."); @@ -148,7 +149,7 @@ private static void LogPackageInformation() { foreach (var package in packageManager.FindPackagesForUser(string.Empty, pfn)) { - Log.Logger()?.ReportInfo($"{package.Id.FullName} Devmode: {package.IsDevelopmentMode} Signature: {package.SignatureKind}"); + Log.Logger()?.ReportInfo($"{package.Id.FullName} DevMode: {package.IsDevelopmentMode} Signature: {package.SignatureKind}"); } } } diff --git a/src/GitHubExtensionServer/Properties/PublishProfiles/win10-arm64.pubxml b/src/GitHubExtensionServer/Properties/PublishProfiles/win10-arm64.pubxml index c5e8be62..2593bad1 100644 --- a/src/GitHubExtensionServer/Properties/PublishProfiles/win10-arm64.pubxml +++ b/src/GitHubExtensionServer/Properties/PublishProfiles/win10-arm64.pubxml @@ -1,17 +1,17 @@ - - - - - FileSystem - arm64 - win10-arm64 - true - False - False - True - True - True - + + + + + FileSystem + arm64 + win10-arm64 + true + False + False + True + True + True + \ No newline at end of file diff --git a/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x64.pubxml b/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x64.pubxml index e8e2c0a3..8b6ea06a 100644 --- a/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x64.pubxml +++ b/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x64.pubxml @@ -1,17 +1,17 @@ - - - - - FileSystem - x64 - win10-x64 - true - False - False - True - True - True - + + + + + FileSystem + x64 + win10-x64 + true + False + False + True + True + True + \ No newline at end of file diff --git a/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x86.pubxml b/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x86.pubxml index e601cec4..99985aca 100644 --- a/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x86.pubxml +++ b/src/GitHubExtensionServer/Properties/PublishProfiles/win10-x86.pubxml @@ -1,17 +1,17 @@ - - - - - FileSystem - x86 - win10-x86 - true - False - False - True - True - True - + + + + + FileSystem + x86 + win10-x86 + true + False + False + True + True + True + \ No newline at end of file diff --git a/src/Logging/DevHome.Logging.csproj b/src/Logging/DevHome.Logging.csproj index b88a011e..e6e1d303 100644 --- a/src/Logging/DevHome.Logging.csproj +++ b/src/Logging/DevHome.Logging.csproj @@ -1,4 +1,4 @@ - + @@ -14,16 +14,4 @@ Spectre - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/src/Logging/helpers/DictionaryExtensions.cs b/src/Logging/helpers/DictionaryExtensions.cs index ef55d694..fdc54273 100644 --- a/src/Logging/helpers/DictionaryExtensions.cs +++ b/src/Logging/helpers/DictionaryExtensions.cs @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging.Helpers; - -public static class DictionaryExtensions -{ - public static void DisposeAll(this IDictionary dictionary) - { - if (dictionary is null) - { - throw new ArgumentNullException(nameof(dictionary)); - } - - foreach (var kv in dictionary) - { - if (kv.Key is IDisposable keyDisposable) - { - keyDisposable.Dispose(); - } - - if (kv.Value is IDisposable valDisposable) - { - valDisposable.Dispose(); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging.Helpers; + +public static class DictionaryExtensions +{ + public static void DisposeAll(this IDictionary dictionary) + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + foreach (var kv in dictionary) + { + if (kv.Key is IDisposable keyDisposable) + { + keyDisposable.Dispose(); + } + + if (kv.Value is IDisposable valDisposable) + { + valDisposable.Dispose(); + } + } + } +} diff --git a/src/Logging/helpers/FileSystem.cs b/src/Logging/helpers/FileSystem.cs index 234bbc8e..c8679349 100644 --- a/src/Logging/helpers/FileSystem.cs +++ b/src/Logging/helpers/FileSystem.cs @@ -1,35 +1,36 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging.Helpers; -public class FileSystem -{ - public static string BuildOutputFilename(string filename, string outputFolder, bool createPathIfNecessary = true) - { - var outputFilename = SubstituteOutputFilename(filename, outputFolder); - var file = new FileInfo(outputFilename); - if (createPathIfNecessary) - { - file.Directory?.Create(); - } - - return file.FullName; - } - - public static string SubstituteNow(string s) - { - if (s.Contains("{now}", StringComparison.CurrentCulture)) - { - var now = DateTime.Now; - var nowAsString = $"{now:yyyyMMdd-HHmmss}"; - return s.Replace("{now}", nowAsString); - } - - return s; - } - - public static string SubstituteOutputFilename(string filename, string outputDirectory) - { - return Path.Combine(SubstituteNow(outputDirectory), SubstituteNow(filename)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging.Helpers; + +public class FileSystem +{ + public static string BuildOutputFilename(string filename, string outputFolder, bool createPathIfNecessary = true) + { + var outputFilename = SubstituteOutputFilename(filename, outputFolder); + var file = new FileInfo(outputFilename); + if (createPathIfNecessary) + { + file.Directory?.Create(); + } + + return file.FullName; + } + + public static string SubstituteNow(string s) + { + if (s.Contains("{now}", StringComparison.CurrentCulture)) + { + var now = DateTime.Now; + var nowAsString = $"{now:yyyyMMdd-HHmmss}"; + return s.Replace("{now}", nowAsString); + } + + return s; + } + + public static string SubstituteOutputFilename(string filename, string outputDirectory) + { + return Path.Combine(SubstituteNow(outputDirectory), SubstituteNow(filename)); + } +} diff --git a/src/Logging/helpers/StringExtensions.cs b/src/Logging/helpers/StringExtensions.cs index bdc9acfc..24eb5c4d 100644 --- a/src/Logging/helpers/StringExtensions.cs +++ b/src/Logging/helpers/StringExtensions.cs @@ -1,15 +1,16 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System.Globalization; - -namespace DevHome.Logging.Helpers; -public static class StringExtensions -{ - public static string ToStringInvariant(this T value) => Convert.ToString(value, CultureInfo.InvariantCulture)!; - - public static string FormatInvariant(this string value, params object[] arguments) - { - return string.Format(CultureInfo.InvariantCulture, value, arguments); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Globalization; + +namespace DevHome.Logging.Helpers; + +public static class StringExtensions +{ + public static string ToStringInvariant(this T value) => Convert.ToString(value, CultureInfo.InvariantCulture)!; + + public static string FormatInvariant(this string value, params object[] arguments) + { + return string.Format(CultureInfo.InvariantCulture, value, arguments); + } +} diff --git a/src/Logging/listeners/DebugListener.cs b/src/Logging/listeners/DebugListener.cs index 6eb7e1c0..2f5fa8f1 100644 --- a/src/Logging/listeners/DebugListener.cs +++ b/src/Logging/listeners/DebugListener.cs @@ -1,11 +1,12 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; using System.Globalization; using System.Text; namespace DevHome.Logging.Listeners; + public class DebugListener : ListenerBase { public DebugListener(string name) diff --git a/src/Logging/listeners/DebugListenerOptions.cs b/src/Logging/listeners/DebugListenerOptions.cs index dacd9b26..bdf9fd4a 100644 --- a/src/Logging/listeners/DebugListenerOptions.cs +++ b/src/Logging/listeners/DebugListenerOptions.cs @@ -1,7 +1,8 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace DevHome.Logging; + public partial class Options { public bool DebugListenerEnabled { get; set; } = true; diff --git a/src/Logging/listeners/IListener.cs b/src/Logging/listeners/IListener.cs index 65e574e5..6bb582ec 100644 --- a/src/Logging/listeners/IListener.cs +++ b/src/Logging/listeners/IListener.cs @@ -1,19 +1,20 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging.Listeners; -public interface IListener -{ - string Name - { - get; - } - - ILoggerHost? Host - { - get; - set; - } - - void HandleLogEvent(LogEvent evt); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging.Listeners; + +public interface IListener +{ + string Name + { + get; + } + + ILoggerHost? Host + { + get; + set; + } + + void HandleLogEvent(LogEvent evt); +} diff --git a/src/Logging/listeners/ListenerBase.cs b/src/Logging/listeners/ListenerBase.cs index 8b9c6be8..03c37bdc 100644 --- a/src/Logging/listeners/ListenerBase.cs +++ b/src/Logging/listeners/ListenerBase.cs @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging.Listeners; - -public abstract class ListenerBase : IListener -{ - public ILoggerHost? Host - { - get; - set; - } - - public Options? Options => Host?.Options; - - public string Name - { - get; - } - - public ListenerBase(string name) - { - Host = null; - Name = name; - } - - public abstract void HandleLogEvent(LogEvent evt); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging.Listeners; + +public abstract class ListenerBase : IListener +{ + public ILoggerHost? Host + { + get; + set; + } + + public Options? Options => Host?.Options; + + public string Name + { + get; + } + + public ListenerBase(string name) + { + Host = null; + Name = name; + } + + public abstract void HandleLogEvent(LogEvent evt); +} diff --git a/src/Logging/listeners/LogFileListener.cs b/src/Logging/listeners/LogFileListener.cs index 8a293978..72d505e8 100644 --- a/src/Logging/listeners/LogFileListener.cs +++ b/src/Logging/listeners/LogFileListener.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; namespace DevHome.Logging.Listeners; + public class LogFileListener : ListenerBase, IDisposable { private readonly TextWriter? writer; diff --git a/src/Logging/listeners/LogFileListenerOptions.cs b/src/Logging/listeners/LogFileListenerOptions.cs index 2e035bce..b30594bc 100644 --- a/src/Logging/listeners/LogFileListenerOptions.cs +++ b/src/Logging/listeners/LogFileListenerOptions.cs @@ -1,25 +1,26 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging; -public partial class Options -{ - private const string LogFileNameDefault = "DevHomeGitHubExtension.log"; - private const string LogFileFolderNameDefault = "{now}"; - - public string LogFileName { get; set; } = LogFileNameDefault; - - public string LogFileFolderName { get; set; } = LogFileFolderNameDefault; - - // The Temp Path is used for storage by default so tests can run this code without being packaged. - // If we directly put in the ApplicationData folder, it would fail anytime the program was not packaged. - // For use with packaged application, set in Options to: - // ApplicationData.Current.TemporaryFolder.Path - public string LogFileFolderRoot { get; set; } = Path.GetTempPath(); - - public string LogFileFolderPath => Path.Combine(LogFileFolderRoot, LogFileFolderName); - - public bool LogFileEnabled { get; set; } = true; - - public SeverityLevel LogFileFilter { get; set; } = SeverityLevel.Info; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging; + +public partial class Options +{ + private const string LogFileNameDefault = "DevHomeGitHubExtension.log"; + private const string LogFileFolderNameDefault = "{now}"; + + public string LogFileName { get; set; } = LogFileNameDefault; + + public string LogFileFolderName { get; set; } = LogFileFolderNameDefault; + + // The Temp Path is used for storage by default so tests can run this code without being packaged. + // If we directly put in the ApplicationData folder, it would fail anytime the program was not packaged. + // For use with packaged application, set in Options to: + // ApplicationData.Current.TemporaryFolder.Path + public string LogFileFolderRoot { get; set; } = Path.GetTempPath(); + + public string LogFileFolderPath => Path.Combine(LogFileFolderRoot, LogFileFolderName); + + public bool LogFileEnabled { get; set; } = true; + + public SeverityLevel LogFileFilter { get; set; } = SeverityLevel.Info; +} diff --git a/src/Logging/listeners/StdoutListener.cs b/src/Logging/listeners/StdoutListener.cs index 3a9ab4e9..c8e89543 100644 --- a/src/Logging/listeners/StdoutListener.cs +++ b/src/Logging/listeners/StdoutListener.cs @@ -1,132 +1,133 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System.Globalization; - -namespace DevHome.Logging.Listeners; -public class StdoutListener : ListenerBase -{ - private static readonly ConsoleColor CDefaultColor = ConsoleColor.White; - private static readonly ConsoleColor CDebugColor = ConsoleColor.DarkGray; - private static readonly ConsoleColor CInfoColor = ConsoleColor.White; - private static readonly ConsoleColor CWarnColor = ConsoleColor.Yellow; - private static readonly ConsoleColor CErrorColor = ConsoleColor.Red; - private static readonly ConsoleColor CCriticalColor = ConsoleColor.Magenta; - private static readonly ConsoleColor CExceptionColor = ConsoleColor.Red; - private static readonly ConsoleColor CElapsedColor = ConsoleColor.Green; - private static readonly ConsoleColor CSourceColor = ConsoleColor.Cyan; - - // 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 (); - - public StdoutListener(string name) - : base(name) - { - } - - public override void HandleLogEvent(LogEvent evt) - { - ConsoleHandleLogEvent(evt, true); - } - - private void ConsoleHandleLogEvent(LogEvent evt, bool newline) - { - ConsoleHandleLogEvent(evt, newline, LogEvent.NoElapsed); - } - - private void ConsoleHandleLogEvent(LogEvent evt, bool newline, TimeSpan elapsed) - { - if (!MeetsFilter(evt)) - { - return; - } - - var line = new List> - { - Tuple.Create(CDefaultColor, "["), - Tuple.Create(CSourceColor, (evt.SubSource != null) ? $"{evt.Source}/{evt.SubSource}" : $"{evt.Source}"), - Tuple.Create(CDefaultColor, "] "), - Tuple.Create(GetSeverityColor(evt.Severity), evt.Severity.ToString().ToUpper(CultureInfo.InvariantCulture)), - Tuple.Create(CDefaultColor, ": "), - Tuple.Create(GetSeverityColor(evt.Severity), evt.Message), - }; - - if (elapsed != LogEvent.NoElapsed) - { - line.Add(Tuple.Create(CDefaultColor, " [")); - line.Add(Tuple.Create(CElapsedColor, $"Elapsed: {elapsed:hh\\:mm\\:ss\\.ffffff}")); - line.Add(Tuple.Create(CDefaultColor, "]")); - } - - if (evt.Exception != null) - { - line.Add(Tuple.Create(CExceptionColor, $"{Environment.NewLine}{evt.Exception}")); - } - - if (newline) - { - line.Add(Tuple.Create(CDefaultColor, Environment.NewLine)); - } - - // Do this in a static lock to prevent tearing. - lock (_stdoutLock) - { - try - { - WriteColor(line); - Console.ResetColor(); - Console.Out.Flush(); - } - catch - { - } - } - } - - private bool MeetsFilter(LogEvent evt) - { - return evt.Severity >= Options?.LogStdoutFilter; - } - - private void WriteColor(List> strings) - { - if (strings == null) - { - return; - } - - foreach (var s in strings) - { - WriteColor(s); - } - } - - private void WriteColor(Tuple s) - { - if (Console.IsOutputRedirected) - { - Console.Write(s.Item2); - } - else - { - var currentColor = Console.ForegroundColor; - Console.ForegroundColor = s.Item1; - Console.Write(s.Item2); - Console.ForegroundColor = currentColor; - } - } - - private ConsoleColor GetSeverityColor(SeverityLevel severity) - { - return severity switch - { - SeverityLevel.Debug => CDebugColor, - SeverityLevel.Info => CInfoColor, - SeverityLevel.Warn => CWarnColor, - SeverityLevel.Error => CErrorColor, - SeverityLevel.Critical => CCriticalColor, - _ => CDefaultColor, - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Globalization; + +namespace DevHome.Logging.Listeners; + +public class StdoutListener : ListenerBase +{ + private static readonly ConsoleColor CDefaultColor = ConsoleColor.White; + private static readonly ConsoleColor CDebugColor = ConsoleColor.DarkGray; + private static readonly ConsoleColor CInfoColor = ConsoleColor.White; + private static readonly ConsoleColor CWarnColor = ConsoleColor.Yellow; + private static readonly ConsoleColor CErrorColor = ConsoleColor.Red; + private static readonly ConsoleColor CCriticalColor = ConsoleColor.Magenta; + private static readonly ConsoleColor CExceptionColor = ConsoleColor.Red; + private static readonly ConsoleColor CElapsedColor = ConsoleColor.Green; + private static readonly ConsoleColor CSourceColor = ConsoleColor.Cyan; + + // 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 (); + + public StdoutListener(string name) + : base(name) + { + } + + public override void HandleLogEvent(LogEvent evt) + { + ConsoleHandleLogEvent(evt, true); + } + + private void ConsoleHandleLogEvent(LogEvent evt, bool newline) + { + ConsoleHandleLogEvent(evt, newline, LogEvent.NoElapsed); + } + + private void ConsoleHandleLogEvent(LogEvent evt, bool newline, TimeSpan elapsed) + { + if (!MeetsFilter(evt)) + { + return; + } + + var line = new List> + { + Tuple.Create(CDefaultColor, "["), + Tuple.Create(CSourceColor, (evt.SubSource != null) ? $"{evt.Source}/{evt.SubSource}" : $"{evt.Source}"), + Tuple.Create(CDefaultColor, "] "), + Tuple.Create(GetSeverityColor(evt.Severity), evt.Severity.ToString().ToUpper(CultureInfo.InvariantCulture)), + Tuple.Create(CDefaultColor, ": "), + Tuple.Create(GetSeverityColor(evt.Severity), evt.Message), + }; + + if (elapsed != LogEvent.NoElapsed) + { + line.Add(Tuple.Create(CDefaultColor, " [")); + line.Add(Tuple.Create(CElapsedColor, $"Elapsed: {elapsed:hh\\:mm\\:ss\\.ffffff}")); + line.Add(Tuple.Create(CDefaultColor, "]")); + } + + if (evt.Exception != null) + { + line.Add(Tuple.Create(CExceptionColor, $"{Environment.NewLine}{evt.Exception}")); + } + + if (newline) + { + line.Add(Tuple.Create(CDefaultColor, Environment.NewLine)); + } + + // Do this in a static lock to prevent tearing. + lock (_stdoutLock) + { + try + { + WriteColor(line); + Console.ResetColor(); + Console.Out.Flush(); + } + catch + { + } + } + } + + private bool MeetsFilter(LogEvent evt) + { + return evt.Severity >= Options?.LogStdoutFilter; + } + + private void WriteColor(List> strings) + { + if (strings == null) + { + return; + } + + foreach (var s in strings) + { + WriteColor(s); + } + } + + private void WriteColor(Tuple s) + { + if (Console.IsOutputRedirected) + { + Console.Write(s.Item2); + } + else + { + var currentColor = Console.ForegroundColor; + Console.ForegroundColor = s.Item1; + Console.Write(s.Item2); + Console.ForegroundColor = currentColor; + } + } + + private ConsoleColor GetSeverityColor(SeverityLevel severity) + { + return severity switch + { + SeverityLevel.Debug => CDebugColor, + SeverityLevel.Info => CInfoColor, + SeverityLevel.Warn => CWarnColor, + SeverityLevel.Error => CErrorColor, + SeverityLevel.Critical => CCriticalColor, + _ => CDefaultColor, + }; + } +} diff --git a/src/Logging/listeners/StdoutListenerOptions.cs b/src/Logging/listeners/StdoutListenerOptions.cs index f0e1fb0d..bbfe3aad 100644 --- a/src/Logging/listeners/StdoutListenerOptions.cs +++ b/src/Logging/listeners/StdoutListenerOptions.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging; -public partial class Options -{ - public bool LogStdoutEnabled { get; set; } = true; - - public SeverityLevel LogStdoutFilter { get; set; } = SeverityLevel.Info; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging; + +public partial class Options +{ + public bool LogStdoutEnabled { get; set; } = true; + + public SeverityLevel LogStdoutFilter { get; set; } = SeverityLevel.Info; +} diff --git a/src/Logging/logger/ILoggerHost.cs b/src/Logging/logger/ILoggerHost.cs index eaaa25c2..c0852831 100644 --- a/src/Logging/logger/ILoggerHost.cs +++ b/src/Logging/logger/ILoggerHost.cs @@ -1,70 +1,71 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using DevHome.Logging.Listeners; - -namespace DevHome.Logging; -public interface ILoggerHost : IDisposable -{ - string Name - { - get; - } - - Options Options - { - get; - } - - void AddListener(IListener listener); - - void ReportEvent(LogEvent evt); - - void ReportEvent(string component, SeverityLevel severity, string message); - - void ReportEvent(string component, SeverityLevel severity, string message, System.Exception exception); - - void ReportEvent(string component, string subComponent, SeverityLevel severity, string message); - - void ReportEvent(string component, string subComponent, SeverityLevel severity, string message, System.Exception exception); - - void ReportDebug(string component, string message); - - void ReportDebug(string component, string message, Exception exception); - - void ReportDebug(string component, string subComponent, string message); - - void ReportDebug(string component, string subComponent, string message, Exception exception); - - void ReportInfo(string component, string message); - - void ReportInfo(string component, string message, Exception exception); - - void ReportInfo(string component, string subComponent, string message); - - void ReportInfo(string component, string subComponent, string message, Exception exception); - - void ReportWarn(string component, string message); - - void ReportWarn(string component, string message, Exception exception); - - void ReportWarn(string component, string subComponent, string message); - - void ReportWarn(string component, string subComponent, string message, Exception exception); - - void ReportError(string component, string message); - - void ReportError(string component, string message, Exception exception); - - void ReportError(string component, string subComponent, string message); - - void ReportError(string component, string subComponent, string message, Exception exception); - - void ReportCritical(string component, string message); - - void ReportCritical(string component, string message, Exception exception); - - void ReportCritical(string component, string subComponent, string message); - - void ReportCritical(string component, string subComponent, string message, Exception exception); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.Logging.Listeners; + +namespace DevHome.Logging; + +public interface ILoggerHost : IDisposable +{ + string Name + { + get; + } + + Options Options + { + get; + } + + void AddListener(IListener listener); + + void ReportEvent(LogEvent evt); + + void ReportEvent(string component, SeverityLevel severity, string message); + + void ReportEvent(string component, SeverityLevel severity, string message, System.Exception exception); + + void ReportEvent(string component, string subComponent, SeverityLevel severity, string message); + + void ReportEvent(string component, string subComponent, SeverityLevel severity, string message, System.Exception exception); + + void ReportDebug(string component, string message); + + void ReportDebug(string component, string message, Exception exception); + + void ReportDebug(string component, string subComponent, string message); + + void ReportDebug(string component, string subComponent, string message, Exception exception); + + void ReportInfo(string component, string message); + + void ReportInfo(string component, string message, Exception exception); + + void ReportInfo(string component, string subComponent, string message); + + void ReportInfo(string component, string subComponent, string message, Exception exception); + + void ReportWarn(string component, string message); + + void ReportWarn(string component, string message, Exception exception); + + void ReportWarn(string component, string subComponent, string message); + + void ReportWarn(string component, string subComponent, string message, Exception exception); + + void ReportError(string component, string message); + + void ReportError(string component, string message, Exception exception); + + void ReportError(string component, string subComponent, string message); + + void ReportError(string component, string subComponent, string message, Exception exception); + + void ReportCritical(string component, string message); + + void ReportCritical(string component, string message, Exception exception); + + void ReportCritical(string component, string subComponent, string message); + + void ReportCritical(string component, string subComponent, string message, Exception exception); +} diff --git a/src/Logging/logger/LogEvent.cs b/src/Logging/logger/LogEvent.cs index 9664afec..a555a411 100644 --- a/src/Logging/logger/LogEvent.cs +++ b/src/Logging/logger/LogEvent.cs @@ -1,108 +1,109 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using DevHome.Logging.Helpers; - -namespace DevHome.Logging; -public class LogEvent -{ - public string Source - { - get; - } - - public string? SubSource - { - get; - } - - public SeverityLevel Severity - { - get; - } - - public string Message - { - get; - } - - public Exception? Exception - { - get; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.Logging.Helpers; + +namespace DevHome.Logging; + +public class LogEvent +{ + public string Source + { + get; + } + + public string? SubSource + { + get; + } + + public SeverityLevel Severity + { + get; + } + + public string Message + { + get; + } + + public Exception? Exception + { + get; + } public DateTime Created { get; } - - public TimeSpan Elapsed - { - get; - private set; - } - - internal void SetElapsed(TimeSpan elapsed) => Elapsed = elapsed; - - public static long NoElapsedTicks => -1L; - - public static TimeSpan NoElapsed => new (NoElapsedTicks); - - public bool HasElapsed => Elapsed.Ticks >= 0; - - private LogEvent(string source, string subSource, SeverityLevel severity, string message, Exception exception, TimeSpan elapsed) - { - Source = source; - SubSource = subSource; - Severity = severity; - Message = message; - Exception = exception; + + public TimeSpan Elapsed + { + get; + private set; + } + + internal void SetElapsed(TimeSpan elapsed) => Elapsed = elapsed; + + public static long NoElapsedTicks => -1L; + + public static TimeSpan NoElapsed => new (NoElapsedTicks); + + public bool HasElapsed => Elapsed.Ticks >= 0; + + private LogEvent(string source, string subSource, SeverityLevel severity, string message, Exception exception, TimeSpan elapsed) + { + Source = source; + SubSource = subSource; + Severity = severity; + Message = message; + Exception = exception; Elapsed = elapsed; - Created = DateTime.UtcNow; - } - - public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message) => Create(source, subSource, severity, message, null, NoElapsed); - - public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message, Exception exception) => Create(source, subSource, severity, message, exception, NoElapsed); - - 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 string FullSourceName - { - get - { - if (SubSource != null) - { - return $"{Source}/{SubSource}"; - } - else - { - return Source; - } - } - } - - public override string ToString() - { - var hasException = Exception != null; - - if (hasException && HasElapsed) - { - return "[{0}] {1} {2} {3} {4}".FormatInvariant(FullSourceName, Severity.ToString(), Message, Exception!, Elapsed); - } - else if (hasException && !HasElapsed) - { - return "[{0}] {1} {2} {3}".FormatInvariant(FullSourceName, Severity.ToString(), Message, Exception!); - } - else if (!hasException && HasElapsed) - { - return "[{0}] {1} {2} {3}".FormatInvariant(FullSourceName, Severity.ToString(), Message, Elapsed); - } - else - { - return "[{0}] {1} {2}".FormatInvariant(FullSourceName, Severity.ToString(), Message); - } - } -} + Created = DateTime.UtcNow; + } + + public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message) => Create(source, subSource, severity, message, null, NoElapsed); + + public static LogEvent Create(string source, string subSource, SeverityLevel severity, string message, Exception exception) => Create(source, subSource, severity, message, exception, NoElapsed); + + 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 string FullSourceName + { + get + { + if (SubSource != null) + { + return $"{Source}/{SubSource}"; + } + else + { + return Source; + } + } + } + + public override string ToString() + { + var hasException = Exception != null; + + if (hasException && HasElapsed) + { + return "[{0}] {1} {2} {3} {4}".FormatInvariant(FullSourceName, Severity.ToString(), Message, Exception!, Elapsed); + } + else if (hasException && !HasElapsed) + { + return "[{0}] {1} {2} {3}".FormatInvariant(FullSourceName, Severity.ToString(), Message, Exception!); + } + else if (!hasException && HasElapsed) + { + return "[{0}] {1} {2} {3}".FormatInvariant(FullSourceName, Severity.ToString(), Message, Elapsed); + } + else + { + return "[{0}] {1} {2}".FormatInvariant(FullSourceName, Severity.ToString(), Message); + } + } +} diff --git a/src/Logging/logger/Logger.cs b/src/Logging/logger/Logger.cs index f4ed48a2..1601c2ac 100644 --- a/src/Logging/logger/Logger.cs +++ b/src/Logging/logger/Logger.cs @@ -1,11 +1,12 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Concurrent; using DevHome.Logging.Helpers; using DevHome.Logging.Listeners; namespace DevHome.Logging; + public class Logger : ILoggerHost, IDisposable { public Logger(string name, Options options) @@ -13,7 +14,7 @@ public Logger(string name, Options options) Name = name; Options = options; - // Debug Listneer + // Debug listener if (options.DebugListenerEnabled) { var debugListener = new DebugListener("Debug"); diff --git a/src/Logging/logger/Options.cs b/src/Logging/logger/Options.cs index e7247484..d0cbfb12 100644 --- a/src/Logging/logger/Options.cs +++ b/src/Logging/logger/Options.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging; -public partial class Options : ICloneable -{ - public FailFastSeverityLevel FailFastSeverity { get; set; } = FailFastSeverityLevel.Critical; - - public object Clone() => MemberwiseClone(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging; + +public partial class Options : ICloneable +{ + public FailFastSeverityLevel FailFastSeverity { get; set; } = FailFastSeverityLevel.Critical; + + public object Clone() => MemberwiseClone(); +} diff --git a/src/Logging/logger/SeverityLevel.cs b/src/Logging/logger/SeverityLevel.cs index 0471f04f..a5565a3d 100644 --- a/src/Logging/logger/SeverityLevel.cs +++ b/src/Logging/logger/SeverityLevel.cs @@ -1,37 +1,38 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace DevHome.Logging; -public enum SeverityLevel -{ - Debug, - Info, - Warn, - Error, - Critical, -} - -// For setting fail-fast behavior, at what level of failure will we conduct a fail-fast? -// This is mostly intended for specifying whether any error or any critical error causes an FailFast. -// By default we assume any critical failure is by definition something we should not continue after detecting. -public enum FailFastSeverityLevel -{ - Ignore = -1, - Warning = SeverityLevel.Warn, - Error = SeverityLevel.Error, - Critical = SeverityLevel.Critical, -} - -public class FailFast -{ - public static bool IsFailFastSeverityLevel(SeverityLevel severity, FailFastSeverityLevel failFastSeverity) - { - return failFastSeverity switch - { - FailFastSeverityLevel.Warning => severity >= SeverityLevel.Warn, - FailFastSeverityLevel.Error => severity >= SeverityLevel.Error, - FailFastSeverityLevel.Critical => severity >= SeverityLevel.Critical, - _ => false, - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Logging; + +public enum SeverityLevel +{ + Debug, + Info, + Warn, + Error, + Critical, +} + +// For setting fail-fast behavior, at what level of failure will we conduct a fail-fast? +// This is mostly intended for specifying whether any error or any critical error causes an FailFast. +// By default we assume any critical failure is by definition something we should not continue after detecting. +public enum FailFastSeverityLevel +{ + Ignore = -1, + Warning = SeverityLevel.Warn, + Error = SeverityLevel.Error, + Critical = SeverityLevel.Critical, +} + +public class FailFast +{ + public static bool IsFailFastSeverityLevel(SeverityLevel severity, FailFastSeverityLevel failFastSeverity) + { + return failFastSeverity switch + { + FailFastSeverityLevel.Warning => severity >= SeverityLevel.Warn, + FailFastSeverityLevel.Error => severity >= SeverityLevel.Error, + FailFastSeverityLevel.Critical => severity >= SeverityLevel.Critical, + _ => false, + }; + } +} diff --git a/src/Telemetry/GitHubExtension.Telemetry.csproj b/src/Telemetry/GitHubExtension.Telemetry.csproj index 7b46d781..5647434e 100644 --- a/src/Telemetry/GitHubExtension.Telemetry.csproj +++ b/src/Telemetry/GitHubExtension.Telemetry.csproj @@ -1,28 +1,16 @@ - - - - - GitHubExtension.Telemetry - x86;x64;arm64 - win10-x86;win10-x64;win10-arm64 - true - - - - - Guard - Spectre - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + GitHubExtension.Telemetry + x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 + + + + + Guard + Spectre + + + diff --git a/src/Telemetry/ILogger.cs b/src/Telemetry/ILogger.cs index 2a14d53d..965b543b 100644 --- a/src/Telemetry/ILogger.cs +++ b/src/Telemetry/ILogger.cs @@ -1,67 +1,67 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Diagnostics.CodeAnalysis; - -namespace GitHubExtension.Telemetry; - -/// -/// To create an instance call LoggerFactory.Get() -/// -public interface ILogger -{ - /// - /// Add a string that we should try stripping out of some of our telemetry for sensitivity reasons (ex. VM name, etc.). - /// We can never be 100% sure we can remove every string, but this should greatly reduce us collecting PII. - /// Note that the order in which AddSensitive is called matters, as later when we call ReplaceSensitiveStrings, it will try - /// finding and replacing the earlier strings first. This can be helpful, since we can target specific - /// strings (like username) first, which should help preserve more information helpful for diagnosis. - /// - /// Sensitive string to add (ex. "c:\xyz") - /// string to replace it with (ex. "-path-") - public void AddSensitiveString(string name, string replaceWith); - - /// - /// Gets a value indicating whether telemetry is on - /// For future use if we add a registry key or some other setting to check if telemetry is turned on. - public bool IsTelemetryOn { get; } - - /// - /// Logs an exception at Measure level. To log at Critical level, the event name needs approval. - /// - /// What we trying to do when the exception occurred. - /// Exception object - /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - public void LogException(string action, Exception e, Guid? relatedActivityId = null); - - /// - /// Log the time an action took (ex. time spent on a tool). - /// - /// The measurement we're performing (ex. "DeployTime"). - /// How long the action took in milliseconds. - /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - public void LogTimeTaken(string eventName, uint timeTakenMilliseconds, Guid? relatedActivityId = null); - - /// - /// Log an informational event. Typically used for just a single event that's only called one place in the code. - /// If you are logging the same event multiple times, it's best to add a helper method in Logger - /// - /// Name of the error event - /// Determines whether to upload the data to our servers, and on how many machines. - /// Values to send to the telemetry system. - /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - /// Anonymous type. - public void Log<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null); - - /// - /// Log an error event. Typically used for just a single event that's only called one place in the code. - /// If you are logging the same event multiple times, it's best to add a helper method in Logger - /// - /// Name of the error event - /// Determines whether to upload the data to our servers, and on how many machines. - /// Values to send to the telemetry system. - /// Optional Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - /// Anonymous type. - public void LogError<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null); -} + +namespace GitHubExtension.Telemetry; + +/// +/// To create an instance call LoggerFactory.Get() +/// +public interface ILogger +{ + /// + /// Add a string that we should try stripping out of some of our telemetry for sensitivity reasons (ex. VM name, etc.). + /// We can never be 100% sure we can remove every string, but this should greatly reduce us collecting PII. + /// Note that the order in which AddSensitive is called matters, as later when we call ReplaceSensitiveStrings, it will try + /// finding and replacing the earlier strings first. This can be helpful, since we can target specific + /// strings (like username) first, which should help preserve more information helpful for diagnosis. + /// + /// Sensitive string to add (ex. "c:\xyz") + /// string to replace it with (ex. "-path-") + public void AddSensitiveString(string name, string replaceWith); + + /// + /// Gets a value indicating whether telemetry is on + /// For future use if we add a registry key or some other setting to check if telemetry is turned on. + public bool IsTelemetryOn { get; } + + /// + /// Logs an exception at Measure level. To log at Critical level, the event name needs approval. + /// + /// What we trying to do when the exception occurred. + /// Exception object + /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + public void LogException(string action, Exception e, Guid? relatedActivityId = null); + + /// + /// Log the time an action took (ex. time spent on a tool). + /// + /// The measurement we're performing (ex. "DeployTime"). + /// How long the action took in milliseconds. + /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + public void LogTimeTaken(string eventName, uint timeTakenMilliseconds, Guid? relatedActivityId = null); + + /// + /// Log an informational event. Typically used for just a single event that's only called one place in the code. + /// If you are logging the same event multiple times, it's best to add a helper method in Logger + /// + /// Name of the error event + /// Determines whether to upload the data to our servers, and on how many machines. + /// Values to send to the telemetry system. + /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + /// Anonymous type. + public void Log<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null); + + /// + /// Log an error event. Typically used for just a single event that's only called one place in the code. + /// If you are logging the same event multiple times, it's best to add a helper method in Logger + /// + /// Name of the error event + /// Determines whether to upload the data to our servers, and on how many machines. + /// Values to send to the telemetry system. + /// Optional Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + /// Anonymous type. + public void LogError<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null); +} diff --git a/src/Telemetry/LogLevel.cs b/src/Telemetry/LogLevel.cs index 0917717e..6acd6347 100644 --- a/src/Telemetry/LogLevel.cs +++ b/src/Telemetry/LogLevel.cs @@ -1,39 +1,39 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace GitHubExtension.Telemetry; - -/// -/// Telemetry Levels. -/// These levels are defined by our telemetry system, so it's possible the sampling -/// could change in the future. -/// There aren't any convenient enums we can consume, so create our own. -/// -public enum LogLevel -{ - /// - /// Local. - /// Only log telemetry locally on the machine (similar to an ETW event). - /// - Local, - - /// - /// Info. - /// Send telemetry from internal and flighted machines, but no external retail machines. - /// - Info, - - /// - /// Measure. - /// Send telemetry from internal and flighted machines, plus a small, sample % of retail machines. - /// Should only be used for telemetry we use to derive measures from. - /// - Measure, - - /// - /// Critical. - /// Send telemetry from all devices sampled at 100%. - /// Should only be used for approved events. - /// - Critical, -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GitHubExtension.Telemetry; + +/// +/// Telemetry Levels. +/// These levels are defined by our telemetry system, so it's possible the sampling +/// could change in the future. +/// There aren't any convenient enums we can consume, so create our own. +/// +public enum LogLevel +{ + /// + /// Local. + /// Only log telemetry locally on the machine (similar to an ETW event). + /// + Local, + + /// + /// Info. + /// Send telemetry from internal and flighted machines, but no external retail machines. + /// + Info, + + /// + /// Measure. + /// Send telemetry from internal and flighted machines, plus a small, sample % of retail machines. + /// Should only be used for telemetry we use to derive measures from. + /// + Measure, + + /// + /// Critical. + /// Send telemetry from all devices sampled at 100%. + /// Should only be used for approved events. + /// + Critical, +} diff --git a/src/Telemetry/Logger.cs b/src/Telemetry/Logger.cs index fb223746..21af0ee8 100644 --- a/src/Telemetry/Logger.cs +++ b/src/Telemetry/Logger.cs @@ -1,266 +1,266 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Tracing; -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.Diagnostics.Telemetry; - -namespace GitHubExtension.Telemetry; - -internal class Logger : ILogger -{ - private const string ProviderName = "Microsoft.GitHubExtension"; - - /// - /// Time Taken Event Name - /// - private const string TimeTakenEventName = "TimeTaken"; - - /// - /// Exception Thrown Event Name - /// - private const string ExceptionThrownEventName = "ExceptionThrown"; - - private static readonly Guid DefaultRelatedActivityId = Guid.Empty; - - /// - /// Can only have one EventSource alive per process, so just create one statically. - /// - private static readonly EventSource TelemetryEventSourceInstance = new TelemetryEventSource(ProviderName); - - /// - /// 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 }; - - /// - /// Logs error telemetry locally, but shouldn't upload it. Similar to an ETW event. - /// - 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 }; - - /// - /// 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 }; - - /// - /// 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 }; - - /// - /// 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 }; - - /// - /// Logs critical telemetry. - /// This should be sent back on all devices sampled at 100%. - /// - 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 }; - - /// - /// ActivityId so we can correlate all events in the same run - /// - private static Guid activityId = Guid.NewGuid(); - - /// - /// List of strings we should try removing for sensitivity reasons. - /// - private readonly List> sensitiveStrings = new (); - - /// - /// Initializes a new instance of the class. - /// Prevents a default instance of the Logger class from being created. - /// - internal Logger() - { - } - - /// - /// Gets a value indicating whether telemetry is on - /// For future use if we add a registry key or some other setting to check if telemetry is turned on. - public bool IsTelemetryOn => true; - - /// - /// Add a string that we should try stripping out of some of our telemetry for sensitivity reasons (ex. VM name, etc.). - /// We can never be 100% sure we can remove every string, but this should greatly reduce us collecting PII. - /// Note that the order in which AddSensitive is called matters, as later when we call ReplaceSensitiveStrings, it will try - /// finding and replacing the earlier strings first. This can be helpful, since we can target specific - /// strings (like username) first, which should help preserve more information helpful for diagnosis. - /// - /// Sensitive string to add (ex. "c:\xyz") - /// string to replace it with (ex. "-path-") - public void AddSensitiveString(string name, string replaceWith) - { - // Make sure the name isn't blank, hasn't already been added, and is greater than three characters. - // Otherwise they could name their VM "a", and then we would end up replacing every "a" with another string. - if (!string.IsNullOrWhiteSpace(name) && name.Length > 3 && !sensitiveStrings.Exists(item => name.Equals(item.Key, StringComparison.Ordinal))) - { - sensitiveStrings.Add(new KeyValuePair(name, replaceWith ?? string.Empty)); - } - } - - /// - /// Logs an exception at Measure level. To log at Critical level, the event name needs approval. - /// - /// What we trying to do when the exception occurred. - /// Exception object - /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - public void LogException(string action, Exception e, Guid? relatedActivityId = null) - { - var innerMessage = ReplaceSensitiveStrings(e.InnerException?.Message); - var innerStackTrace = new StringBuilder(); - var innerException = e.InnerException; - while (innerException != null) - { - innerStackTrace.Append(innerException.StackTrace); - - // Separating by 2 new lines to distinguish between different exceptions. - innerStackTrace.AppendLine(); - innerStackTrace.AppendLine(); - innerException = innerException.InnerException; - } - - LogError( - ExceptionThrownEventName, - LogLevel.Measure, - new - { - action, - name = e.GetType().Name, - stackTrace = e.StackTrace, - innerName = e.InnerException?.GetType().Name, - innerMessage, - innerStackTrace = innerStackTrace.ToString(), - message = ReplaceSensitiveStrings(e.Message), - }, - relatedActivityId ?? DefaultRelatedActivityId); - } - - /// - /// Log the time an action took (ex. deploy time). - /// - /// The measurement we're performing (ex. "DeployTime"). - /// How long the action took in milliseconds. - /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - public void LogTimeTaken(string eventName, uint timeTakenMilliseconds, Guid? relatedActivityId = null) - { - Log( - TimeTakenEventName, - LogLevel.Critical, - new - { - eventName, - timeTakenMilliseconds, - }, - relatedActivityId ?? DefaultRelatedActivityId); - } - - /// - /// Log an informational event. Typically used for just a single event that's only called one place in the code. - /// - /// Name of the error event - /// Determines whether to upload the data to our servers, and on how many machines. - /// Values to send to the telemetry system. - /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - /// Anonymous type. - public void Log<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null) - { - WriteTelemetryEvent(eventName, level, relatedActivityId ?? DefaultRelatedActivityId, false, data); - } - - /// - /// Log an error event. Typically used for just a single event that's only called one place in the code. - /// - /// Name of the error event - /// Determines whether to upload the data to our servers, and on how many machines. - /// Values to send to the telemetry system. - /// Optional Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them - /// Anonymous type. - public void LogError<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null) - { - WriteTelemetryEvent(eventName, level, relatedActivityId ?? DefaultRelatedActivityId, true, data); - } - - /// - /// Replaces sensitive strings in a string with non sensitive strings. - /// - /// Before, unstripped string. - /// After, stripped string - private string ReplaceSensitiveStrings(string message) - { - if (message != null) - { - foreach (var pair in sensitiveStrings) - { - // There's no String.Replace() with case insensitivity. - // We could use Regular Expressions here for searching for case-insensitive string matches, - // but it's not easy to specify the RegEx timeout value for .net 4.0. And we were worried - // about rare cases where the user could accidentally lock us up with RegEx, since we're using strings - // provided by the user, so just use a simple non-RegEx replacement algorithm instead. - var sb = new StringBuilder(); - var i = 0; - while (true) - { - // Find the string to strip out. - var foundPosition = message.IndexOf(pair.Key, i, StringComparison.OrdinalIgnoreCase); - if (foundPosition < 0) - { - sb.Append(message, i, message.Length - i); - message = sb.ToString(); - break; - } - - // Replace the string. - sb.Append(message, i, foundPosition - i); - sb.Append(pair.Value); - i = foundPosition + pair.Key.Length; - } - } - } - - return message; +using System.Diagnostics.Tracing; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Diagnostics.Telemetry; + +namespace GitHubExtension.Telemetry; + +internal class Logger : ILogger +{ + private const string ProviderName = "Microsoft.GitHubExtension"; + + /// + /// Time Taken Event Name + /// + private const string TimeTakenEventName = "TimeTaken"; + + /// + /// Exception Thrown Event Name + /// + private const string ExceptionThrownEventName = "ExceptionThrown"; + + private static readonly Guid DefaultRelatedActivityId = Guid.Empty; + + /// + /// Can only have one EventSource alive per process, so just create one statically. + /// + private static readonly EventSource TelemetryEventSourceInstance = new TelemetryEventSource(ProviderName); + + /// + /// 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 }; + + /// + /// Logs error telemetry locally, but shouldn't upload it. Similar to an ETW event. + /// + 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 }; + + /// + /// 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 }; + + /// + /// 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 }; + + /// + /// 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 }; + + /// + /// Logs critical telemetry. + /// This should be sent back on all devices sampled at 100%. + /// + 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 }; + + /// + /// ActivityId so we can correlate all events in the same run + /// + private static Guid activityId = Guid.NewGuid(); + + /// + /// List of strings we should try removing for sensitivity reasons. + /// + private readonly List> sensitiveStrings = new (); + + /// + /// Initializes a new instance of the class. + /// Prevents a default instance of the Logger class from being created. + /// + internal Logger() + { } - /// - /// Writes the telemetry event info using the TraceLogging API. - /// - /// Anonymous type. - /// Name of the event. - /// Determines whether to upload the data to our servers, and the sample set of host machines. - /// Set to true if an error condition raised this event. + /// + /// Gets a value indicating whether telemetry is on + /// For future use if we add a registry key or some other setting to check if telemetry is turned on. + public bool IsTelemetryOn => true; + + /// + /// Add a string that we should try stripping out of some of our telemetry for sensitivity reasons (ex. VM name, etc.). + /// We can never be 100% sure we can remove every string, but this should greatly reduce us collecting PII. + /// Note that the order in which AddSensitive is called matters, as later when we call ReplaceSensitiveStrings, it will try + /// finding and replacing the earlier strings first. This can be helpful, since we can target specific + /// strings (like username) first, which should help preserve more information helpful for diagnosis. + /// + /// Sensitive string to add (ex. "c:\xyz") + /// string to replace it with (ex. "-path-") + public void AddSensitiveString(string name, string replaceWith) + { + // Make sure the name isn't blank, hasn't already been added, and is greater than three characters. + // Otherwise they could name their VM "a", and then we would end up replacing every "a" with another string. + if (!string.IsNullOrWhiteSpace(name) && name.Length > 3 && !sensitiveStrings.Exists(item => name.Equals(item.Key, StringComparison.Ordinal))) + { + sensitiveStrings.Add(new KeyValuePair(name, replaceWith ?? string.Empty)); + } + } + + /// + /// Logs an exception at Measure level. To log at Critical level, the event name needs approval. + /// + /// What we trying to do when the exception occurred. + /// Exception object + /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + public void LogException(string action, Exception e, Guid? relatedActivityId = null) + { + var innerMessage = ReplaceSensitiveStrings(e.InnerException?.Message); + var innerStackTrace = new StringBuilder(); + var innerException = e.InnerException; + while (innerException != null) + { + innerStackTrace.Append(innerException.StackTrace); + + // Separating by 2 new lines to distinguish between different exceptions. + innerStackTrace.AppendLine(); + innerStackTrace.AppendLine(); + innerException = innerException.InnerException; + } + + LogError( + ExceptionThrownEventName, + LogLevel.Measure, + new + { + action, + name = e.GetType().Name, + stackTrace = e.StackTrace, + innerName = e.InnerException?.GetType().Name, + innerMessage, + innerStackTrace = innerStackTrace.ToString(), + message = ReplaceSensitiveStrings(e.Message), + }, + relatedActivityId ?? DefaultRelatedActivityId); + } + + /// + /// Log the time an action took (ex. deploy time). + /// + /// The measurement we're performing (ex. "DeployTime"). + /// How long the action took in milliseconds. + /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + public void LogTimeTaken(string eventName, uint timeTakenMilliseconds, Guid? relatedActivityId = null) + { + Log( + TimeTakenEventName, + LogLevel.Critical, + new + { + eventName, + timeTakenMilliseconds, + }, + relatedActivityId ?? DefaultRelatedActivityId); + } + + /// + /// Log an informational event. Typically used for just a single event that's only called one place in the code. + /// + /// Name of the error event + /// Determines whether to upload the data to our servers, and on how many machines. + /// Values to send to the telemetry system. + /// Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + /// Anonymous type. + public void Log<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null) + { + WriteTelemetryEvent(eventName, level, relatedActivityId ?? DefaultRelatedActivityId, false, data); + } + + /// + /// Log an error event. Typically used for just a single event that's only called one place in the code. + /// + /// Name of the error event + /// Determines whether to upload the data to our servers, and on how many machines. + /// Values to send to the telemetry system. + /// Optional Optional relatedActivityId which will allow to correlate this telemetry with other telemetry in the same action/activity or thread and corelate them + /// Anonymous type. + public void LogError<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, T data, Guid? relatedActivityId = null) + { + WriteTelemetryEvent(eventName, level, relatedActivityId ?? DefaultRelatedActivityId, true, data); + } + + /// + /// Replaces sensitive strings in a string with non sensitive strings. + /// + /// Before, unstripped string. + /// After, stripped string + private string ReplaceSensitiveStrings(string message) + { + if (message != null) + { + foreach (var pair in sensitiveStrings) + { + // There's no String.Replace() with case insensitivity. + // We could use Regular Expressions here for searching for case-insensitive string matches, + // but it's not easy to specify the RegEx timeout value for .net 4.0. And we were worried + // about rare cases where the user could accidentally lock us up with RegEx, since we're using strings + // provided by the user, so just use a simple non-RegEx replacement algorithm instead. + var sb = new StringBuilder(); + var i = 0; + while (true) + { + // Find the string to strip out. + var foundPosition = message.IndexOf(pair.Key, i, StringComparison.OrdinalIgnoreCase); + if (foundPosition < 0) + { + sb.Append(message, i, message.Length - i); + message = sb.ToString(); + break; + } + + // Replace the string. + sb.Append(message, i, foundPosition - i); + sb.Append(pair.Value); + i = foundPosition + pair.Key.Length; + } + } + } + + return message; + } + + /// + /// Writes the telemetry event info using the TraceLogging API. + /// + /// Anonymous type. + /// Name of the event. + /// Determines whether to upload the data to our servers, and the sample set of host machines. + /// Set to true if an error condition raised this event. /// Values to send to the telemetry system. [UnconditionalSuppressMessage( "ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The type passed for data is an anonymous type consisting of primitive type properties declared in an assembly that is not marked trimmable.")] - private void WriteTelemetryEvent<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, Guid relatedActivityId, bool isError, T data) - { - EventSourceOptions telemetryOptions; - if (IsTelemetryOn) - { + Justification = "The type passed for data is an anonymous type consisting of primitive type properties declared in an assembly that is not marked trimmable.")] + private void WriteTelemetryEvent<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, LogLevel level, Guid relatedActivityId, bool isError, T data) + { + EventSourceOptions telemetryOptions; + if (IsTelemetryOn) + { telemetryOptions = level switch { LogLevel.Critical => isError ? Logger.CriticalDataErrorOption : Logger.CriticalDataOption, @@ -268,34 +268,34 @@ private string ReplaceSensitiveStrings(string message) LogLevel.Info => isError ? Logger.InfoErrorOption : Logger.InfoOption, _ => isError ? Logger.LocalErrorOption : Logger.LocalOption, }; - } - else - { - // The telemetry is not turned on, downgrade to local telemetry - telemetryOptions = isError ? Logger.LocalErrorOption : Logger.LocalOption; - } - - TelemetryEventSourceInstance.Write(eventName, ref telemetryOptions, ref activityId, ref relatedActivityId, ref data); - } - - internal void AddWellKnownSensitiveStrings() - { - try - { - // This should convert "c:\users\johndoe" to "". - var userDirectory = Directory.GetParent(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)).FullName; - AddSensitiveString(Directory.GetParent(userDirectory).ToString(), ""); - - // Include both these names, since they should cover the logged on user, and the user who is running the tools built on top of these API's - // These names should almost always be the same, but technically could be different. - AddSensitiveString(Environment.UserName, ""); - var currentUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Split('\\').Last(); - AddSensitiveString(currentUserName, ""); - } - catch (Exception e) - { - // Catch and log exception - LogException("AddSensitiveStrings", e); - } - } -} + } + else + { + // The telemetry is not turned on, downgrade to local telemetry + telemetryOptions = isError ? Logger.LocalErrorOption : Logger.LocalOption; + } + + TelemetryEventSourceInstance.Write(eventName, ref telemetryOptions, ref activityId, ref relatedActivityId, ref data); + } + + internal void AddWellKnownSensitiveStrings() + { + try + { + // This should convert "c:\users\johndoe" to "". + var userDirectory = Directory.GetParent(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)).FullName; + AddSensitiveString(Directory.GetParent(userDirectory).ToString(), ""); + + // Include both these names, since they should cover the logged on user, and the user who is running the tools built on top of these API's + // These names should almost always be the same, but technically could be different. + AddSensitiveString(Environment.UserName, ""); + var currentUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Split('\\').Last(); + AddSensitiveString(currentUserName, ""); + } + catch (Exception e) + { + // Catch and log exception + LogException("AddSensitiveStrings", e); + } + } +} diff --git a/src/Telemetry/LoggerFactory.cs b/src/Telemetry/LoggerFactory.cs index 28a4abb1..600c85a9 100644 --- a/src/Telemetry/LoggerFactory.cs +++ b/src/Telemetry/LoggerFactory.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Telemetry; diff --git a/src/Telemetry/TelemetryEventSource.cs b/src/Telemetry/TelemetryEventSource.cs index 40a98a0a..46f4cbb1 100644 --- a/src/Telemetry/TelemetryEventSource.cs +++ b/src/Telemetry/TelemetryEventSource.cs @@ -1,343 +1,343 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -#if TELEMETRYEVENTSOURCE_USE_NUGET -using Microsoft.Diagnostics.Tracing; -#else -using System.Diagnostics.Tracing; -#endif -using System; -using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; - -#pragma warning disable 3021 // 'type' does not need a CLSCompliant attribute - -namespace Microsoft.Diagnostics.Telemetry -{ - /// - /// - /// An EventSource with extra methods and constants commonly used in Microsoft's - /// TraceLogging-based ETW. This class inherits from EventSource, and is exactly - /// the same as EventSource except that it always enables - /// EtwSelfDescribingEventFormat and never uses traits. It also provides several - /// constants and helpers commonly used by Microsoft code. - /// - /// - /// Different versions of this class use different provider traits. The provider - /// traits in this class are empty. As a result, providers using this class will - /// not join any ETW Provider Groups and will not be given any special treatment - /// by group-sensitive ETW listeners. - /// - /// - /// When including this class in your project, you may define the following - /// conditional-compilation symbols to adjust the default behaviors: - /// - /// - /// TELEMETRYEVENTSOURCE_USE_NUGET - use Microsoft.Diagnostics.Tracing instead - /// of System.Diagnostics.Tracing. - /// - /// - /// TELEMETRYEVENTSOURCE_PUBLIC - define TelemetryEventSource as public instead - /// of internal. - /// - /// -#if TELEMETRYEVENTSOURCE_PUBLIC - public -#else - internal -#endif - class TelemetryEventSource - : EventSource - { - /// - /// Keyword 0x0000100000000000 is reserved for future definition. Do - /// not use keyword 0x0000100000000000 in Microsoft-style ETW. - /// - public const EventKeywords Reserved44Keyword = (EventKeywords)0x0000100000000000; - - /// - /// Add TelemetryKeyword to eventSourceOptions.Keywords to indicate that - /// an event is for general-purpose telemetry. - /// This keyword should not be combined with MeasuresKeyword or - /// CriticalDataKeyword. - /// - public const EventKeywords TelemetryKeyword = (EventKeywords)0x0000200000000000; - - /// - /// Add MeasuresKeyword to eventSourceOptions.Keywords to indicate that - /// an event is for understanding measures and reporting scenarios. - /// This keyword should not be combined with TelemetryKeyword or - /// CriticalDataKeyword. - /// - public const EventKeywords MeasuresKeyword = (EventKeywords)0x0000400000000000; - - /// - /// Add CriticalDataKeyword to eventSourceOptions.Keywords to indicate that - /// an event powers user experiences or is critical to business intelligence. - /// This keyword should not be combined with TelemetryKeyword or - /// MeasuresKeyword. - /// - public const EventKeywords CriticalDataKeyword = (EventKeywords)0x0000800000000000; - - /// - /// Add CostDeferredLatency to eventSourceOptions.Tags to indicate that an event - /// should try to upload over free networks for a period of time before resorting - /// to upload over costed networks. - /// - public const EventTags CostDeferredLatency = (EventTags)0x040000; - - /// - /// Add CoreData to eventSourceOptions.Tags to indicate that an event - /// contains high priority "core data". - /// - public const EventTags CoreData = (EventTags)0x00080000; - - /// - /// Add InjectXToken to eventSourceOptions.Tags to indicate that an XBOX - /// identity token should be injected into the event before the event is - /// uploaded. - /// - public const EventTags InjectXToken = (EventTags)0x00100000; - - /// - /// Add RealtimeLatency to eventSourceOptions.Tags to indicate that an event - /// should be transmitted in real time (via any available connection). - /// - public const EventTags RealtimeLatency = (EventTags)0x0200000; - - /// - /// Add NormalLatency to eventSourceOptions.Tags to indicate that an event - /// should be transmitted via the preferred connection based on device policy. - /// - public const EventTags NormalLatency = (EventTags)0x0400000; - - /// - /// Add CriticalPersistence to eventSourceOptions.Tags to indicate that an - /// event should be deleted last when low on spool space. - /// - public const EventTags CriticalPersistence = (EventTags)0x0800000; - - /// - /// Add NormalPersistence to eventSourceOptions.Tags to indicate that an event - /// should be deleted first when low on spool space. - /// - public const EventTags NormalPersistence = (EventTags)0x1000000; - - /// - /// Add DropPii to eventSourceOptions.Tags to indicate that an event contains - /// PII and should be anonymized by the telemetry client. If this tag is - /// present, PartA fields that might allow identification or cross-event - /// correlation will be removed from the event. - /// - public const EventTags DropPii = (EventTags)0x02000000; - - /// - /// Add HashPii to eventSourceOptions.Tags to indicate that an event contains - /// PII and should be anonymized by the telemetry client. If this tag is - /// present, PartA fields that might allow identification or cross-event - /// correlation will be hashed (obfuscated). - /// - public const EventTags HashPii = (EventTags)0x04000000; - - /// - /// Add MarkPii to eventSourceOptions.Tags to indicate that an event contains - /// PII but may be uploaded as-is. If this tag is present, the event will be - /// marked so that it will only appear on the private stream. - /// - public const EventTags MarkPii = (EventTags)0x08000000; - - /// - /// Add DropPiiField to eventFieldAttribute.Tags to indicate that a field - /// contains PII and should be dropped by the telemetry client. - /// - public const EventFieldTags DropPiiField = (EventFieldTags)0x04000000; - - /// - /// Add HashPiiField to eventFieldAttribute.Tags to indicate that a field - /// contains PII and should be hashed (obfuscated) prior to uploading. - /// - public const EventFieldTags HashPiiField = (EventFieldTags)0x08000000; - - /// - /// Constructs a new instance of the TelemetryEventSource class with the - /// specified name. Sets the EtwSelfDescribingEventFormat option. - /// - /// The name of the event source. - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] - public TelemetryEventSource( - string eventSourceName) - : base( - eventSourceName, - EventSourceSettings.EtwSelfDescribingEventFormat) - { - return; - } - - /// - /// For use by derived classes that set the eventSourceName via EventSourceAttribute. - /// Sets the EtwSelfDescribingEventFormat option. - /// - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] - protected TelemetryEventSource() - : base( - EventSourceSettings.EtwSelfDescribingEventFormat) - { - return; - } - - /// - /// Constructs a new instance of the TelemetryEventSource class with the - /// specified name. Sets the EtwSelfDescribingEventFormat option. - /// - /// The name of the event source. - /// The parameter is not used. - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] - [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "API compatibility")] - public TelemetryEventSource( - string eventSourceName, - TelemetryGroup telemetryGroup) - : base( - eventSourceName, - EventSourceSettings.EtwSelfDescribingEventFormat) - { - return; - } - - /// - /// Returns an instance of EventSourceOptions with the TelemetryKeyword set. - /// - /// Returns an instance of EventSourceOptions with the TelemetryKeyword set. - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] - public static EventSourceOptions TelemetryOptions() - { - return new EventSourceOptions { Keywords = TelemetryKeyword }; - } - - /// - /// Returns an instance of EventSourceOptions with the MeasuresKeyword set. - /// - /// Returns an instance of EventSourceOptions with the MeasuresKeyword set. - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] - public static EventSourceOptions MeasuresOptions() - { - return new EventSourceOptions { Keywords = MeasuresKeyword }; - } - } - - /// - /// - /// The PrivTags class defines privacy tags that can be used to specify the privacy - /// category of an event. Add a privacy tag as a field with name "PartA_PrivTags". - /// As a shortcut, you can use _1 as the field name, which will automatically be - /// expanded to "PartA_PrivTags" at runtime. - /// - /// - /// Multiple tags can be OR'ed together if necessary (rarely needed). - /// - /// - /// - /// Typical usage: - /// - /// es.Write("UsageEvent", new - /// { - /// _1 = PrivTags.ProductAndServiceUsage, - /// field1 = fieldValue1, - /// field2 = fieldValue2 - /// }); - /// - /// -#if TELEMETRYEVENTSOURCE_PUBLIC - [CLSCompliant(false)] - public -#else - internal -#endif - static class PrivTags - { - /// - public const Internal.PartA_PrivTags BrowsingHistory = Internal.PartA_PrivTags.BrowsingHistory; - - /// - public const Internal.PartA_PrivTags DeviceConnectivityAndConfiguration = Internal.PartA_PrivTags.DeviceConnectivityAndConfiguration; - - /// - public const Internal.PartA_PrivTags InkingTypingAndSpeechUtterance = Internal.PartA_PrivTags.InkingTypingAndSpeechUtterance; - - /// - public const Internal.PartA_PrivTags ProductAndServicePerformance = Internal.PartA_PrivTags.ProductAndServicePerformance; - - /// - public const Internal.PartA_PrivTags ProductAndServiceUsage = Internal.PartA_PrivTags.ProductAndServiceUsage; - - /// - public const Internal.PartA_PrivTags SoftwareSetupAndInventory = Internal.PartA_PrivTags.SoftwareSetupAndInventory; - } - /// - /// Pass a TelemetryGroup value to the constructor of TelemetryEventSource - /// to control which telemetry group should be joined. - /// Note: has no effect in this version of TelemetryEventSource. - /// -#if TELEMETRYEVENTSOURCE_PUBLIC - public -#else - internal -#endif - enum TelemetryGroup - { - /// - /// The default group. Join this group to log normal, non-critical, non-coredata - /// events. - /// - MicrosoftTelemetry, - - /// - /// Join this group to log CriticalData, CoreData, or other specially approved - /// events. - /// - WindowsCoreTelemetry - } - -#pragma warning disable SA1403 // File may only contain a single namespace - namespace Internal -#pragma warning restore SA1403 // File may only contain a single namespace - { - /// - /// The complete list of privacy tags supported for events. - /// Multiple tags can be OR'ed together if an event belongs in multiple - /// categories. - /// Note that the PartA_PrivTags enum should not be used directly. - /// Instead, use values from the PrivTags class. - /// - [Flags] -#if TELEMETRYEVENTSOURCE_PUBLIC - [CLSCompliant(false)] - public -#else - internal -#endif - enum PartA_PrivTags - : ulong - { - /// - None = 0, - - /// - BrowsingHistory = 0x0000000000000002u, - - /// - DeviceConnectivityAndConfiguration = 0x0000000000000800u, - - /// - InkingTypingAndSpeechUtterance = 0x0000000000020000u, - - /// - ProductAndServicePerformance = 0x0000000001000000u, - - /// - ProductAndServiceUsage = 0x0000000002000000u, - - /// - SoftwareSetupAndInventory = 0x0000000080000000u, - } - } -} +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#if TELEMETRYEVENTSOURCE_USE_NUGET +using Microsoft.Diagnostics.Tracing; +#else +using System.Diagnostics.Tracing; +#endif +using System; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; + +#pragma warning disable 3021 // 'type' does not need a CLSCompliant attribute + +namespace Microsoft.Diagnostics.Telemetry +{ + /// + /// + /// An EventSource with extra methods and constants commonly used in Microsoft's + /// TraceLogging-based ETW. This class inherits from EventSource, and is exactly + /// the same as EventSource except that it always enables + /// EtwSelfDescribingEventFormat and never uses traits. It also provides several + /// constants and helpers commonly used by Microsoft code. + /// + /// + /// Different versions of this class use different provider traits. The provider + /// traits in this class are empty. As a result, providers using this class will + /// not join any ETW Provider Groups and will not be given any special treatment + /// by group-sensitive ETW listeners. + /// + /// + /// When including this class in your project, you may define the following + /// conditional-compilation symbols to adjust the default behaviors: + /// + /// + /// TELEMETRYEVENTSOURCE_USE_NUGET - use Microsoft.Diagnostics.Tracing instead + /// of System.Diagnostics.Tracing. + /// + /// + /// TELEMETRYEVENTSOURCE_PUBLIC - define TelemetryEventSource as public instead + /// of internal. + /// + /// +#if TELEMETRYEVENTSOURCE_PUBLIC + public +#else + internal +#endif + class TelemetryEventSource + : EventSource + { + /// + /// Keyword 0x0000100000000000 is reserved for future definition. Do + /// not use keyword 0x0000100000000000 in Microsoft-style ETW. + /// + public const EventKeywords Reserved44Keyword = (EventKeywords)0x0000100000000000; + + /// + /// Add TelemetryKeyword to eventSourceOptions.Keywords to indicate that + /// an event is for general-purpose telemetry. + /// This keyword should not be combined with MeasuresKeyword or + /// CriticalDataKeyword. + /// + public const EventKeywords TelemetryKeyword = (EventKeywords)0x0000200000000000; + + /// + /// Add MeasuresKeyword to eventSourceOptions.Keywords to indicate that + /// an event is for understanding measures and reporting scenarios. + /// This keyword should not be combined with TelemetryKeyword or + /// CriticalDataKeyword. + /// + public const EventKeywords MeasuresKeyword = (EventKeywords)0x0000400000000000; + + /// + /// Add CriticalDataKeyword to eventSourceOptions.Keywords to indicate that + /// an event powers user experiences or is critical to business intelligence. + /// This keyword should not be combined with TelemetryKeyword or + /// MeasuresKeyword. + /// + public const EventKeywords CriticalDataKeyword = (EventKeywords)0x0000800000000000; + + /// + /// Add CostDeferredLatency to eventSourceOptions.Tags to indicate that an event + /// should try to upload over free networks for a period of time before resorting + /// to upload over costed networks. + /// + public const EventTags CostDeferredLatency = (EventTags)0x040000; + + /// + /// Add CoreData to eventSourceOptions.Tags to indicate that an event + /// contains high priority "core data". + /// + public const EventTags CoreData = (EventTags)0x00080000; + + /// + /// Add InjectXToken to eventSourceOptions.Tags to indicate that an XBOX + /// identity token should be injected into the event before the event is + /// uploaded. + /// + public const EventTags InjectXToken = (EventTags)0x00100000; + + /// + /// Add RealtimeLatency to eventSourceOptions.Tags to indicate that an event + /// should be transmitted in real time (via any available connection). + /// + public const EventTags RealtimeLatency = (EventTags)0x0200000; + + /// + /// Add NormalLatency to eventSourceOptions.Tags to indicate that an event + /// should be transmitted via the preferred connection based on device policy. + /// + public const EventTags NormalLatency = (EventTags)0x0400000; + + /// + /// Add CriticalPersistence to eventSourceOptions.Tags to indicate that an + /// event should be deleted last when low on spool space. + /// + public const EventTags CriticalPersistence = (EventTags)0x0800000; + + /// + /// Add NormalPersistence to eventSourceOptions.Tags to indicate that an event + /// should be deleted first when low on spool space. + /// + public const EventTags NormalPersistence = (EventTags)0x1000000; + + /// + /// Add DropPii to eventSourceOptions.Tags to indicate that an event contains + /// PII and should be anonymized by the telemetry client. If this tag is + /// present, PartA fields that might allow identification or cross-event + /// correlation will be removed from the event. + /// + public const EventTags DropPii = (EventTags)0x02000000; + + /// + /// Add HashPii to eventSourceOptions.Tags to indicate that an event contains + /// PII and should be anonymized by the telemetry client. If this tag is + /// present, PartA fields that might allow identification or cross-event + /// correlation will be hashed (obfuscated). + /// + public const EventTags HashPii = (EventTags)0x04000000; + + /// + /// Add MarkPii to eventSourceOptions.Tags to indicate that an event contains + /// PII but may be uploaded as-is. If this tag is present, the event will be + /// marked so that it will only appear on the private stream. + /// + public const EventTags MarkPii = (EventTags)0x08000000; + + /// + /// Add DropPiiField to eventFieldAttribute.Tags to indicate that a field + /// contains PII and should be dropped by the telemetry client. + /// + public const EventFieldTags DropPiiField = (EventFieldTags)0x04000000; + + /// + /// Add HashPiiField to eventFieldAttribute.Tags to indicate that a field + /// contains PII and should be hashed (obfuscated) prior to uploading. + /// + public const EventFieldTags HashPiiField = (EventFieldTags)0x08000000; + + /// + /// Constructs a new instance of the TelemetryEventSource class with the + /// specified name. Sets the EtwSelfDescribingEventFormat option. + /// + /// The name of the event source. + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] + public TelemetryEventSource( + string eventSourceName) + : base( + eventSourceName, + EventSourceSettings.EtwSelfDescribingEventFormat) + { + return; + } + + /// + /// For use by derived classes that set the eventSourceName via EventSourceAttribute. + /// Sets the EtwSelfDescribingEventFormat option. + /// + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] + protected TelemetryEventSource() + : base( + EventSourceSettings.EtwSelfDescribingEventFormat) + { + return; + } + + /// + /// Constructs a new instance of the TelemetryEventSource class with the + /// specified name. Sets the EtwSelfDescribingEventFormat option. + /// + /// The name of the event source. + /// The parameter is not used. + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "API compatibility")] + public TelemetryEventSource( + string eventSourceName, + TelemetryGroup telemetryGroup) + : base( + eventSourceName, + EventSourceSettings.EtwSelfDescribingEventFormat) + { + return; + } + + /// + /// Returns an instance of EventSourceOptions with the TelemetryKeyword set. + /// + /// Returns an instance of EventSourceOptions with the TelemetryKeyword set. + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] + public static EventSourceOptions TelemetryOptions() + { + return new EventSourceOptions { Keywords = TelemetryKeyword }; + } + + /// + /// Returns an instance of EventSourceOptions with the MeasuresKeyword set. + /// + /// Returns an instance of EventSourceOptions with the MeasuresKeyword set. + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")] + public static EventSourceOptions MeasuresOptions() + { + return new EventSourceOptions { Keywords = MeasuresKeyword }; + } + } + + /// + /// + /// The PrivTags class defines privacy tags that can be used to specify the privacy + /// category of an event. Add a privacy tag as a field with name "PartA_PrivTags". + /// As a shortcut, you can use _1 as the field name, which will automatically be + /// expanded to "PartA_PrivTags" at runtime. + /// + /// + /// Multiple tags can be OR'ed together if necessary (rarely needed). + /// + /// + /// + /// Typical usage: + /// + /// es.Write("UsageEvent", new + /// { + /// _1 = PrivTags.ProductAndServiceUsage, + /// field1 = fieldValue1, + /// field2 = fieldValue2 + /// }); + /// + /// +#if TELEMETRYEVENTSOURCE_PUBLIC + [CLSCompliant(false)] + public +#else + internal +#endif + static class PrivTags + { + /// + public const Internal.PartA_PrivTags BrowsingHistory = Internal.PartA_PrivTags.BrowsingHistory; + + /// + public const Internal.PartA_PrivTags DeviceConnectivityAndConfiguration = Internal.PartA_PrivTags.DeviceConnectivityAndConfiguration; + + /// + public const Internal.PartA_PrivTags InkingTypingAndSpeechUtterance = Internal.PartA_PrivTags.InkingTypingAndSpeechUtterance; + + /// + public const Internal.PartA_PrivTags ProductAndServicePerformance = Internal.PartA_PrivTags.ProductAndServicePerformance; + + /// + public const Internal.PartA_PrivTags ProductAndServiceUsage = Internal.PartA_PrivTags.ProductAndServiceUsage; + + /// + public const Internal.PartA_PrivTags SoftwareSetupAndInventory = Internal.PartA_PrivTags.SoftwareSetupAndInventory; + } + /// + /// Pass a TelemetryGroup value to the constructor of TelemetryEventSource + /// to control which telemetry group should be joined. + /// Note: has no effect in this version of TelemetryEventSource. + /// +#if TELEMETRYEVENTSOURCE_PUBLIC + public +#else + internal +#endif + enum TelemetryGroup + { + /// + /// The default group. Join this group to log normal, non-critical, non-coredata + /// events. + /// + MicrosoftTelemetry, + + /// + /// Join this group to log CriticalData, CoreData, or other specially approved + /// events. + /// + WindowsCoreTelemetry + } + +#pragma warning disable SA1403 // File may only contain a single namespace + namespace Internal +#pragma warning restore SA1403 // File may only contain a single namespace + { + /// + /// The complete list of privacy tags supported for events. + /// Multiple tags can be OR'ed together if an event belongs in multiple + /// categories. + /// Note that the PartA_PrivTags enum should not be used directly. + /// Instead, use values from the PrivTags class. + /// + [Flags] +#if TELEMETRYEVENTSOURCE_PUBLIC + [CLSCompliant(false)] + public +#else + internal +#endif + enum PartA_PrivTags + : ulong + { + /// + None = 0, + + /// + BrowsingHistory = 0x0000000000000002u, + + /// + DeviceConnectivityAndConfiguration = 0x0000000000000800u, + + /// + InkingTypingAndSpeechUtterance = 0x0000000000020000u, + + /// + ProductAndServicePerformance = 0x0000000001000000u, + + /// + ProductAndServiceUsage = 0x0000000002000000u, + + /// + SoftwareSetupAndInventory = 0x0000000080000000u, + } + } +} diff --git a/test/Console/GitHubExtension.TestConsole.csproj b/test/Console/GitHubExtension.TestConsole.csproj index 858ae271..cc2ab6f7 100644 --- a/test/Console/GitHubExtension.TestConsole.csproj +++ b/test/Console/GitHubExtension.TestConsole.csproj @@ -39,20 +39,7 @@ False - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/test/Console/Program.cs b/test/Console/Program.cs index 6f99f5e1..d6d9c0b5 100644 --- a/test/Console/Program.cs +++ b/test/Console/Program.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension; diff --git a/test/GitHubExtension/DataStore/DataManagerTests.cs b/test/GitHubExtension/DataStore/DataManagerTests.cs index ebddafd8..fc9313f8 100644 --- a/test/GitHubExtension/DataStore/DataManagerTests.cs +++ b/test/GitHubExtension/DataStore/DataManagerTests.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Test; diff --git a/test/GitHubExtension/DataStore/DataObjectTests.cs b/test/GitHubExtension/DataStore/DataObjectTests.cs index 90504ce8..8af75741 100644 --- a/test/GitHubExtension/DataStore/DataObjectTests.cs +++ b/test/GitHubExtension/DataStore/DataObjectTests.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper.Contrib.Extensions; using DevHome.Logging; @@ -446,9 +446,9 @@ public void ReadAndWriteCheckRun() } // Verify objects work. - var pullrequest = DataModel.PullRequest.GetById(dataStore, 1); - Assert.IsNotNull(pullrequest); - var checksForPullRequest = pullrequest.Checks; + var pullRequest = DataModel.PullRequest.GetById(dataStore, 1); + Assert.IsNotNull(pullRequest); + var checksForPullRequest = pullRequest.Checks; Assert.IsNotNull(checksForPullRequest); Assert.AreEqual(2, checksForPullRequest.Count()); foreach (var check in checksForPullRequest) @@ -457,12 +457,12 @@ public void ReadAndWriteCheckRun() Assert.IsTrue(check.Completed); } - var failedChecks = pullrequest.FailedChecks; + var failedChecks = pullRequest.FailedChecks; Assert.IsNotNull(failedChecks); Assert.AreEqual(1, failedChecks.Count()); - Assert.AreEqual(CheckStatus.Completed, pullrequest.ChecksStatus); - Assert.AreEqual(CheckConclusion.Failure, pullrequest.ChecksConclusion); + Assert.AreEqual(CheckStatus.Completed, pullRequest.ChecksStatus); + Assert.AreEqual(CheckConclusion.Failure, pullRequest.ChecksConclusion); testListener.PrintEventCounts(); Assert.AreEqual(false, testListener.FoundErrors()); @@ -483,7 +483,7 @@ public void ReadAndWriteStatusAndNotification() dataStore.Create(); Assert.IsNotNull(dataStore.Connection); - // Add checkruns. + // Add CheckRuns. var checks = new List { { new CheckRun { HeadSha = "1234abcd", Name = "Build x86", InternalId = 16, ConclusionId = 1, StatusId = 3, DetailsUrl = "https://link/to/failed/build" } }, @@ -506,17 +506,16 @@ public void ReadAndWriteStatusAndNotification() dataStore.Connection.Insert(new PullRequest { Title = "Fix the things", InternalId = 42, Number = 5, HeadSha = "1234abcd", AuthorId = 1, RepositoryId = 1 }); // Add PullRequestStatus - var pullrequest = DataModel.PullRequest.GetById(dataStore, 1); - Assert.IsNotNull(pullrequest); - var prStatus = PullRequestStatus.Add(dataStore, pullrequest); + var pullRequest = DataModel.PullRequest.GetById(dataStore, 1); + Assert.IsNotNull(pullRequest); + var prStatus = PullRequestStatus.Add(dataStore, pullRequest); TestContext?.WriteLine($" PR: {prStatus.PullRequest.Number} Status: {prStatus.Id}: {prStatus.Conclusion} - {prStatus.DetailsUrl}"); Assert.AreEqual("https://link/to/failed/build", prStatus.DetailsUrl); Assert.AreEqual(1, prStatus.ConclusionId); // Create notification from PR Status - var notification = DataModel.Notification.Create(prStatus, NotificationType.CheckRunFailed); - notification = DataModel.Notification.Add(dataStore, notification); + var notification = Notification.Create(dataStore, prStatus, NotificationType.CheckRunFailed); Assert.IsNotNull(notification); Assert.AreEqual("Fix the things", notification.Title); Assert.AreEqual(1, notification.RepositoryId); @@ -529,4 +528,70 @@ public void ReadAndWriteStatusAndNotification() testListener.PrintEventCounts(); Assert.AreEqual(false, testListener.FoundErrors()); } + + [TestMethod] + [TestCategory("Unit")] + public void ReadAndWriteReview() + { + 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); + + using var tx = dataStore.Connection!.BeginTransaction(); + + var reviews = new List + { + { new Review { PullRequestId = 1, AuthorId = 2, Body = "Review 1", InternalId = 16, State = "Approved" } }, + { new Review { PullRequestId = 1, AuthorId = 3, Body = "Review 2", InternalId = 47, State = "Rejected" } }, + }; + + dataStore.Connection.Insert(reviews[0]); + dataStore.Connection.Insert(reviews[1]); + + // Add User record + dataStore.Connection.Insert(new User { Login = "Kittens", InternalId = 6, AvatarUrl = "https://www.microsoft.com", Type = "Cat" }); + dataStore.Connection.Insert(new User { Login = "Doggos", InternalId = 83, AvatarUrl = "https://www.microsoft.com", Type = "Dog" }); + dataStore.Connection.Insert(new User { Login = "Lizards", InternalId = 3, AvatarUrl = "https://www.microsoft.com", Type = "Reptile" }); + + // Add repository record + dataStore.Connection.Insert(new Repository { OwnerId = 1, InternalId = 47, Name = "TestRepo1", Description = "Short Desc", HtmlUrl = "https://www.microsoft.com", DefaultBranch = "main", HasIssues = 1 }); + + // Add PullRequest record + dataStore.Connection.Insert(new PullRequest { Title = "Fix the things", InternalId = 42, HeadSha = "1234abcd", AuthorId = 1, RepositoryId = 1 }); + + tx.Commit(); + + // Verify retrieval and input into data objects. + var dataStoreReviews = dataStore.Connection.GetAll().ToList(); + Assert.AreEqual(2, dataStoreReviews.Count); + foreach (var review in dataStoreReviews) + { + TestContext?.WriteLine($" Review: {review.Id}: {review.Body} - {review.State}"); + + Assert.IsTrue(review.Id == 1 || review.Id == 2); + Assert.AreEqual(review.Body, $"Review {review.Id}"); + Assert.IsTrue(review.AuthorId == review.Id + 1); + } + + // Verify objects work. + var pullRequest = PullRequest.GetById(dataStore, 1); + Assert.IsNotNull(pullRequest); + var reviewsForPullRequest = pullRequest.Reviews; + Assert.IsNotNull(reviewsForPullRequest); + Assert.AreEqual(2, reviewsForPullRequest.Count()); + foreach (var review in reviewsForPullRequest) + { + TestContext?.WriteLine($" PR 1 - Review: {review}"); + Assert.IsTrue(review.PullRequestId == 1); + } + + testListener.PrintEventCounts(); + Assert.AreEqual(false, testListener.FoundErrors()); + } } diff --git a/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs b/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs index 2f7c0554..45e5b77b 100644 --- a/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs +++ b/test/GitHubExtension/DataStore/DataStoreTestsSetup.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Test; diff --git a/test/GitHubExtension/DataStore/OctokitIngestionTests.cs b/test/GitHubExtension/DataStore/OctokitIngestionTests.cs index 64c26379..f916d6da 100644 --- a/test/GitHubExtension/DataStore/OctokitIngestionTests.cs +++ b/test/GitHubExtension/DataStore/OctokitIngestionTests.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dapper.Contrib.Extensions; using GitHubExtension.Client; @@ -528,4 +528,53 @@ public void AddCheckRunFromOctokit() testListener.PrintEventCounts(); Assert.AreEqual(false, testListener.FoundErrors()); } + + [TestMethod] + [TestCategory("LiveData")] + public void AddReviewFromOctokit() + { + using var log = new DevHome.Logging.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); + + var client = GitHubClientProvider.Instance.GetClient(); + + using var tx = dataStore.Connection!.BeginTransaction(); + + var octoPull = client.PullRequest.Get("microsoft", "devhomegithubextension", 260).Result; + var dataStorePull = DataModel.PullRequest.GetOrCreateByOctokitPullRequest(dataStore, octoPull); + + // Get reviews for pull + var octoReviews = client.PullRequest.Review.GetAll("microsoft", "devhomegithubextension", 260).Result; + Assert.IsNotNull(octoReviews); + TestContext?.WriteLine($"Found {octoReviews.Count}"); + foreach (var review in octoReviews) + { + var dsReview = Review.GetOrCreateByOctokitReview(dataStore, review, dataStorePull.Id); + Assert.IsNotNull(dsReview); + } + + tx.Commit(); + + // Verify retrieval and input into data objects and we get the same results. + var dataStoreReviews = dataStore.Connection.GetAll().ToList(); + Assert.IsNotNull(dataStoreReviews); + var reviewsFromPull = dataStorePull.Reviews; + Assert.IsNotNull(reviewsFromPull); + Assert.AreEqual(dataStoreReviews.Count, reviewsFromPull.Count()); + + foreach (var review in reviewsFromPull) + { + TestContext?.WriteLine($" Id: {review.Id} PullId: {review.PullRequestId} InternalId: {review.InternalId} AuthorId: {review.AuthorId} Author:{review.Author.Login} State: {review.State} Submitted: {review.SubmittedAt}"); + } + + testListener.PrintEventCounts(); + Assert.AreEqual(false, testListener.FoundErrors()); + } } diff --git a/test/GitHubExtension/DeveloperId/CredentialVaultTests.cs b/test/GitHubExtension/DeveloperId/CredentialVaultTests.cs new file mode 100644 index 00000000..125ea76e --- /dev/null +++ b/test/GitHubExtension/DeveloperId/CredentialVaultTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net; +using GitHubExtension.DeveloperId; + +namespace GitHubExtension.Test; + +// Unit Tests for CredentialVault +public partial class DeveloperIdTests +{ + [TestMethod] + [TestCategory("Unit")] + public void CredentialVault_CreateSingleton() + { + var credentialVault1 = new CredentialVault("DevHomeGitHubExtensionTest"); + Assert.IsNotNull(credentialVault1); + + var credentialVault2 = new CredentialVault("DevHomeGitHubExtensionTest"); + Assert.IsNotNull(credentialVault2); + + Assert.AreNotEqual(credentialVault1, credentialVault2); + + credentialVault1.RemoveAllCredentials(); + Assert.AreEqual(0, credentialVault1.GetAllCredentials().Count()); + } + + [TestMethod] + [TestCategory("LiveData")] + [DataRow("testuser1")] + [DataRow("https://github.com/testuser2")] + [DataRow("https://RandomWebServer.example/testuser3")] + public void CredentialVault_SaveAndRetrieveCredential(string loginId) + { + var credentialVault = new CredentialVault("DevHomeGitHubExtensionTest"); + Assert.IsNotNull(credentialVault); + Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); + + var nullCredential = credentialVault.GetCredentials(loginId); + Assert.IsNull(nullCredential); + + var testCredential = "testcredential"; + + var credential = new NetworkCredential(null, testCredential).SecurePassword; + credentialVault.SaveCredentials(loginId, credential); + + var retrievedCredential = credentialVault.GetCredentials(loginId); + Assert.IsNotNull(retrievedCredential); + Assert.AreEqual(testCredential, retrievedCredential.Password); + + credentialVault.RemoveAllCredentials(); + Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); + } + + [TestMethod] + [TestCategory("LiveData")] + [DataRow("testuser1")] + [DataRow("https://github.com/testuser2")] + [DataRow("https://RandomWebServer.example/testuser3")] + public void CredentialVault_RemoveAndRetrieveCredential(string loginId) + { + var credentialVault = new CredentialVault("DevHomeGitHubExtensionTest"); + Assert.IsNotNull(credentialVault); + + var testCredential = "testCredential"; + + var credential = new NetworkCredential(null, testCredential).SecurePassword; + credentialVault.SaveCredentials(loginId, credential); + + var retrievedCredential = credentialVault.GetCredentials(loginId); + Assert.IsNotNull(retrievedCredential); + Assert.AreEqual(testCredential, retrievedCredential.Password); + + credentialVault.RemoveCredentials(loginId); + + var nullCredential = credentialVault.GetCredentials(loginId); + Assert.IsNull(nullCredential); + + Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); + } + + [TestMethod] + [TestCategory("LiveData")] + public void CredentialVault_GetAllCredentials() + { + var credentialVault = new CredentialVault("DevHomeGitHubExtensionTest"); + Assert.IsNotNull(credentialVault); + + Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); + + var testLoginId = "testuser1"; + var testCredential = "testCredential"; + + var credential = new NetworkCredential(null, testCredential).SecurePassword; + credentialVault.SaveCredentials(testLoginId, credential); + + Assert.AreEqual(1, credentialVault.GetAllCredentials().Count()); + + credentialVault.RemoveCredentials(testLoginId); + + Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); + + var nullCredential = credentialVault.GetCredentials(testLoginId); + Assert.IsNull(nullCredential); + } +} diff --git a/test/GitHubExtension/DeveloperId/DeveloperIdProviderTests.cs b/test/GitHubExtension/DeveloperId/DeveloperIdProviderTests.cs index b419d8d5..9a9889b9 100644 --- a/test/GitHubExtension/DeveloperId/DeveloperIdProviderTests.cs +++ b/test/GitHubExtension/DeveloperId/DeveloperIdProviderTests.cs @@ -1,11 +1,12 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Net; using GitHubExtension.DeveloperId; using Microsoft.Windows.DevHome.SDK; namespace GitHubExtension.Test; + public partial class DeveloperIdTests { private CredentialVault SetupCleanCredentialVaultClean() @@ -56,6 +57,32 @@ private CredentialVault SetupCredentialVaultWithGHESTestUser() return credentialVault; } + /* Tests depend on the following environment variables: + * DEV_HOME_TEST_GITHUB_COM_USER : Url for a test user on github.com + * DEV_HOME_TEST_GITHUB_COM_PAT : Personal Access Token for the test user on github.com + * DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER_USER : Url for a test user on GHES + * DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER_PAT : Personal Access Token for the test user on the GHES + */ + private CredentialVault SetupCredentialVaultWithMultipleTestUsers() + { + var credentialVault = SetupCleanCredentialVaultClean(); + + var testLoginId = Environment.GetEnvironmentVariable("DEV_HOME_TEST_GITHUB_COM_USER") ?? string.Empty; + var testPassword = Environment.GetEnvironmentVariable("DEV_HOME_TEST_GITHUB_COM_PAT"); + + var testGHESLoginId = Environment.GetEnvironmentVariable("DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER_USER") ?? string.Empty; + var testGHESPassword = Environment.GetEnvironmentVariable("DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER_PAT"); + + var password = new NetworkCredential(null, testPassword).SecurePassword; + credentialVault.SaveCredentials(testLoginId, password); + + var ghesPassword = new NetworkCredential(null, testGHESPassword).SecurePassword; + credentialVault.SaveCredentials(testGHESLoginId, ghesPassword); + + Assert.AreEqual(2, credentialVault.GetAllCredentials().Count()); + return credentialVault; + } + private CredentialVault SetupCredentialVaultWithInvalidTestUser() { var credentialVault = SetupCleanCredentialVaultClean(); @@ -129,6 +156,31 @@ public void DeveloperIdProvider_RestoreAndGetDeveloperIds() Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); } + [TestMethod] + [TestCategory("LiveData")] + public void DeveloperIdProvider_RestoreAndGetMultipleDeveloperIds() + { + // Setup CredentialVault with a dummy testuser and valid PAT for Github.com + var credentialVault = SetupCredentialVaultWithMultipleTestUsers(); + + // Test whether the DeveloperIdProvider can restore the saved credentials + var devIdProvider = DeveloperIdProvider.GetInstance(); + Assert.IsNotNull(devIdProvider); + var result = devIdProvider.GetLoggedInDeveloperIds(); + + Assert.IsNotNull(result); + Assert.AreEqual(ProviderOperationStatus.Success, result.Result.Status); + Assert.AreEqual(2, result.DeveloperIds.Count()); + Assert.AreNotEqual("dummytestuser1", result.DeveloperIds.First().LoginId); + Assert.AreNotEqual("dummytestuser1", result.DeveloperIds.Last().LoginId); + Assert.IsNotNull(new Uri(result.DeveloperIds.First().Url)); + Assert.IsNotNull(new Uri(result.DeveloperIds.Last().Url)); + + // Cleanup + credentialVault.RemoveAllCredentials(); + Assert.AreEqual(0, credentialVault.GetAllCredentials().Count()); + } + [TestMethod] [TestCategory("LiveData")] public void DeveloperIdProvider_GetDeveloperIds_InvalidPAT() diff --git a/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs b/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs index 6a6c000a..950d82e6 100644 --- a/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs +++ b/test/GitHubExtension/DeveloperId/DeveloperIdTestsSetup.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.DeveloperId; diff --git a/test/GitHubExtension/DeveloperId/FunctionalTests.cs b/test/GitHubExtension/DeveloperId/FunctionalTests.cs index 5f6fa780..5c9dba5a 100644 --- a/test/GitHubExtension/DeveloperId/FunctionalTests.cs +++ b/test/GitHubExtension/DeveloperId/FunctionalTests.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.DeveloperId; using Microsoft.Windows.DevHome.SDK; namespace GitHubExtension.Test; + public partial class DeveloperIdTests { [TestMethod] diff --git a/test/GitHubExtension/DeveloperId/LoginUITests.cs b/test/GitHubExtension/DeveloperId/LoginUITests.cs index 8ff7e854..618270b6 100644 --- a/test/GitHubExtension/DeveloperId/LoginUITests.cs +++ b/test/GitHubExtension/DeveloperId/LoginUITests.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using GitHubExtension.DeveloperId; using GitHubExtension.Test.Mocks; diff --git a/test/GitHubExtension/GitHubExtension.Test.csproj b/test/GitHubExtension/GitHubExtension.Test.csproj index aa425c2d..25c16d18 100644 --- a/test/GitHubExtension/GitHubExtension.Test.csproj +++ b/test/GitHubExtension/GitHubExtension.Test.csproj @@ -10,30 +10,18 @@ true resources.pri + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/test/GitHubExtension/GitHubProviderTests.cs b/test/GitHubExtension/GitHubProviderTests.cs index 68f50063..e7666748 100644 --- a/test/GitHubExtension/GitHubProviderTests.cs +++ b/test/GitHubExtension/GitHubProviderTests.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.DevHome.SDK; namespace GitHubExtension.Test; + [TestClass] public partial class GitHubProviderTests { @@ -112,7 +113,7 @@ public void CanClone() [TestMethod] [TestCategory("Unit")] - [Ignore("Ignoring right now until mock repo is implemeneted")] + [Ignore("Ignoring right now until mock repo is implemented")] public void CloneViaMakingRepoObject() { /* diff --git a/test/GitHubExtension/Helpers/FileHelpers.cs b/test/GitHubExtension/Helpers/FileHelpers.cs index f2e9eeb6..db7561d2 100644 --- a/test/GitHubExtension/Helpers/FileHelpers.cs +++ b/test/GitHubExtension/Helpers/FileHelpers.cs @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -namespace GitHubExtension.Test; - -public partial class TestHelpers -{ - public static string CreateUniqueFolderName(string prefix) - { - // This could potentially be too long of a path name, - // but should be OK for now. Keep the prefix short. - return $"{prefix}-{Guid.NewGuid()}"; - } - - public static string GetUniqueFolderPath(string prefix) - { - return Path.Combine(Path.GetTempPath(), CreateUniqueFolderName(prefix)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GitHubExtension.Test; + +public partial class TestHelpers +{ + public static string CreateUniqueFolderName(string prefix) + { + // This could potentially be too long of a path name, + // but should be OK for now. Keep the prefix short. + return $"{prefix}-{Guid.NewGuid()}"; + } + + public static string GetUniqueFolderPath(string prefix) + { + return Path.Combine(Path.GetTempPath(), CreateUniqueFolderName(prefix)); + } +} diff --git a/test/GitHubExtension/Helpers/TestOptions.cs b/test/GitHubExtension/Helpers/TestOptions.cs index 9896b48f..a2b88652 100644 --- a/test/GitHubExtension/Helpers/TestOptions.cs +++ b/test/GitHubExtension/Helpers/TestOptions.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using DevHome.Logging; using GitHubExtension.DataModel; namespace GitHubExtension.Test; + public partial class TestOptions { public Options LogOptions { get; set; } diff --git a/test/GitHubExtension/Helpers/TestSetupHelpers.cs b/test/GitHubExtension/Helpers/TestSetupHelpers.cs index 4a7c9c6a..c7a4a916 100644 --- a/test/GitHubExtension/Helpers/TestSetupHelpers.cs +++ b/test/GitHubExtension/Helpers/TestSetupHelpers.cs @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using DevHome.Logging; using DevHome.Logging.Helpers; using GitHubExtension.DataModel; -namespace GitHubExtension.Test; - -public partial class TestHelpers -{ - private const string DataBaseFileName = "GitHubExtension-Test.db"; - private const string LogFileName = "GitHubExtension-{now}.log"; - - public static void CleanupTempTestOptions(TestOptions options, TestContext context) - { - // We put DataStore and Log into the same path. - var path = options.DataStoreOptions.DataStoreFolderPath; - - // Directory delete will fail if a file has the name of the directory, so to be - // thorough, check for file delete first. - if (File.Exists(path)) - { - context?.WriteLine($"Cleanup: Deleting file {path}"); - File.Delete(path); - } - - if (Directory.Exists(path)) - { - context?.WriteLine($"Cleanup: Deleting folder {path}"); - Directory.Delete(path, true); - } - - // Intentionally not catching IO errors on cleanup, as that indicates a test problem. - } - - public static TestOptions SetupTempTestOptions(TestContext context) - { - // Since all test created locations are ultimately captured in the Options, we will use - // the Options as truth for storing the test location data to keep all of the - // test locations in one data object to simplify test variables we are tracking and - // to be consistent in test setup/cleanup. - var path = GetUniqueFolderPath("GHPT"); +namespace GitHubExtension.Test; + +public partial class TestHelpers +{ + private const string DataBaseFileName = "GitHubExtension-Test.db"; + private const string LogFileName = "GitHubExtension-{now}.log"; + + public static void CleanupTempTestOptions(TestOptions options, TestContext context) + { + // We put DataStore and Log into the same path. + var path = options.DataStoreOptions.DataStoreFolderPath; + + // Directory delete will fail if a file has the name of the directory, so to be + // thorough, check for file delete first. + if (File.Exists(path)) + { + context?.WriteLine($"Cleanup: Deleting file {path}"); + File.Delete(path); + } + + if (Directory.Exists(path)) + { + context?.WriteLine($"Cleanup: Deleting folder {path}"); + Directory.Delete(path, true); + } + + // Intentionally not catching IO errors on cleanup, as that indicates a test problem. + } + + public static TestOptions SetupTempTestOptions(TestContext context) + { + // Since all test created locations are ultimately captured in the Options, we will use + // the Options as truth for storing the test location data to keep all of the + // test locations in one data object to simplify test variables we are tracking and + // to be consistent in test setup/cleanup. + var path = GetUniqueFolderPath("GHPT"); var options = new TestOptions(); options.LogOptions.FailFastSeverity = FailFastSeverityLevel.Ignore; options.LogOptions.LogFileFilter = SeverityLevel.Debug; options.LogOptions.LogFileFolderRoot = path; options.LogOptions.LogFileName = LogFileName; - options.DataStoreOptions.DataStoreFileName = DataBaseFileName; + options.DataStoreOptions.DataStoreFileName = DataBaseFileName; options.DataStoreOptions.DataStoreFolderPath = path; - options.DataStoreOptions.DataStoreSchema = new GitHubDataStoreSchema(); - - context?.WriteLine($"Temp folder for test run is: {GetTempTestFolderPath(options)}"); - context?.WriteLine($"Temp DataStore file path for test run is: {GetDataStoreFilePath(options)}"); - context?.WriteLine($"Temp Log file path for test run is: {GetLogFilePath(options)}"); - return options; - } - - public static string GetTempTestFolderPath(TestOptions options) - { - // For simplicity putting log and datastore in same root folder. - return options.DataStoreOptions.DataStoreFolderPath; - } - - public static string GetDataStoreFilePath(TestOptions options) - { - return Path.Combine(options.DataStoreOptions.DataStoreFolderPath, options.DataStoreOptions.DataStoreFileName); - } - - public static string GetLogFilePath(TestOptions options) + options.DataStoreOptions.DataStoreSchema = new GitHubDataStoreSchema(); + + context?.WriteLine($"Temp folder for test run is: {GetTempTestFolderPath(options)}"); + context?.WriteLine($"Temp DataStore file path for test run is: {GetDataStoreFilePath(options)}"); + context?.WriteLine($"Temp Log file path for test run is: {GetLogFilePath(options)}"); + return options; + } + + public static string GetTempTestFolderPath(TestOptions options) + { + // For simplicity putting log and datastore in same root folder. + return options.DataStoreOptions.DataStoreFolderPath; + } + + public static string GetDataStoreFilePath(TestOptions options) + { + return Path.Combine(options.DataStoreOptions.DataStoreFolderPath, options.DataStoreOptions.DataStoreFileName); + } + + public static string GetLogFilePath(TestOptions options) { - return FileSystem.SubstituteOutputFilename(options.LogOptions.LogFileName, options.LogOptions.LogFileFolderPath); - } -} + return FileSystem.SubstituteOutputFilename(options.LogOptions.LogFileName, options.LogOptions.LogFileFolderPath); + } +} diff --git a/test/GitHubExtension/Initialize.cs b/test/GitHubExtension/Initialize.cs index 46e5962c..d6939da7 100644 --- a/test/GitHubExtension/Initialize.cs +++ b/test/GitHubExtension/Initialize.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.ApplicationModel.DynamicDependency; diff --git a/test/GitHubExtension/Mocks/DeveloperIdProvider.cs b/test/GitHubExtension/Mocks/DeveloperIdProvider.cs index cdca1a29..4800dfef 100644 --- a/test/GitHubExtension/Mocks/DeveloperIdProvider.cs +++ b/test/GitHubExtension/Mocks/DeveloperIdProvider.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Security; using GitHubExtension.DeveloperId; diff --git a/test/GitHubExtension/Mocks/Repository.cs b/test/GitHubExtension/Mocks/Repository.cs index f3899836..0db54163 100644 --- a/test/GitHubExtension/Mocks/Repository.cs +++ b/test/GitHubExtension/Mocks/Repository.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Windows.DevHome.SDK; using Windows.Foundation; namespace GitHubExtension.Test.Mocks; + public class MockRepository : IRepository { public string DisplayName => "Mock Repository"; diff --git a/test/GitHubExtension/TestClass.cs b/test/GitHubExtension/TestClass.cs index d395478f..1bb75d52 100644 --- a/test/GitHubExtension/TestClass.cs +++ b/test/GitHubExtension/TestClass.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; using GitHubExtension.DeveloperId; diff --git a/test/GitHubExtension/TestListener.cs b/test/GitHubExtension/TestListener.cs index a21b5a90..c08f48ec 100644 --- a/test/GitHubExtension/TestListener.cs +++ b/test/GitHubExtension/TestListener.cs @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System.Globalization; using DevHome.Logging; -using DevHome.Logging.Listeners; - -namespace GitHubExtension.Test; - -public class TestListener : ListenerBase -{ - private TestContext? TestContext - { - get; - set; - } - - public Dictionary EventCounts { get; } = new Dictionary(); - - public TestListener(string name, TestContext testContext) - : base(name) - { - TestContext = testContext; - EventCounts.Add(SeverityLevel.Critical, 0); - EventCounts.Add(SeverityLevel.Error, 0); - EventCounts.Add(SeverityLevel.Warn, 0); - EventCounts.Add(SeverityLevel.Info, 0); - EventCounts.Add(SeverityLevel.Debug, 0); - } - - public void Reset() - { - foreach (var kvp in EventCounts) - { - EventCounts[kvp.Key] = 0; - } - } - - public bool FoundErrors() - { - return EventCounts[SeverityLevel.Critical] > 0 || EventCounts[SeverityLevel.Error] > 0; - } - - public override void HandleLogEvent(LogEvent evt) - { - switch (evt.Severity) - { - case SeverityLevel.Critical: - ++EventCounts[SeverityLevel.Critical]; - PrintEvent(evt); - break; - case SeverityLevel.Error: - ++EventCounts[SeverityLevel.Error]; - PrintEvent(evt); - break; - case SeverityLevel.Warn: - ++EventCounts[SeverityLevel.Warn]; - PrintEvent(evt); - break; - case SeverityLevel.Info: - ++EventCounts[SeverityLevel.Info]; - PrintEvent(evt); - break; - case SeverityLevel.Debug: - ++EventCounts[SeverityLevel.Debug]; - - PrintEvent(evt); - break; - } - } - - private void PrintEvent(LogEvent evt) - { - TestContext?.WriteLine($"[{evt.Source}] {evt.Severity.ToString().ToUpper(CultureInfo.InvariantCulture)}: {evt.Message}"); - if (evt.Exception != null) - { - TestContext?.WriteLine(evt.Exception.ToString()); - } - } - - public void PrintEventCounts() - { - TestContext?.WriteLine($"Critical: {EventCounts[SeverityLevel.Critical]} Error: {EventCounts[SeverityLevel.Error]} Warning: {EventCounts[SeverityLevel.Warn]}"); - } -} +using DevHome.Logging.Listeners; + +namespace GitHubExtension.Test; + +public class TestListener : ListenerBase +{ + private TestContext? TestContext + { + get; + set; + } + + public Dictionary EventCounts { get; } = new Dictionary(); + + public TestListener(string name, TestContext testContext) + : base(name) + { + TestContext = testContext; + EventCounts.Add(SeverityLevel.Critical, 0); + EventCounts.Add(SeverityLevel.Error, 0); + EventCounts.Add(SeverityLevel.Warn, 0); + EventCounts.Add(SeverityLevel.Info, 0); + EventCounts.Add(SeverityLevel.Debug, 0); + } + + public void Reset() + { + foreach (var kvp in EventCounts) + { + EventCounts[kvp.Key] = 0; + } + } + + public bool FoundErrors() + { + return EventCounts[SeverityLevel.Critical] > 0 || EventCounts[SeverityLevel.Error] > 0; + } + + public override void HandleLogEvent(LogEvent evt) + { + switch (evt.Severity) + { + case SeverityLevel.Critical: + ++EventCounts[SeverityLevel.Critical]; + PrintEvent(evt); + break; + case SeverityLevel.Error: + ++EventCounts[SeverityLevel.Error]; + PrintEvent(evt); + break; + case SeverityLevel.Warn: + ++EventCounts[SeverityLevel.Warn]; + PrintEvent(evt); + break; + case SeverityLevel.Info: + ++EventCounts[SeverityLevel.Info]; + PrintEvent(evt); + break; + case SeverityLevel.Debug: + ++EventCounts[SeverityLevel.Debug]; + + PrintEvent(evt); + break; + } + } + + private void PrintEvent(LogEvent evt) + { + TestContext?.WriteLine($"[{evt.Source}] {evt.Severity.ToString().ToUpper(CultureInfo.InvariantCulture)}: {evt.Message}"); + if (evt.Exception != null) + { + TestContext?.WriteLine(evt.Exception.ToString()); + } + } + + public void PrintEventCounts() + { + TestContext?.WriteLine($"Critical: {EventCounts[SeverityLevel.Critical]} Error: {EventCounts[SeverityLevel.Error]} Warning: {EventCounts[SeverityLevel.Warn]}"); + } +} diff --git a/test/GitHubExtension/Usings.cs b/test/GitHubExtension/Usings.cs index 77a0d4c5..b5ac66bd 100644 --- a/test/GitHubExtension/Usings.cs +++ b/test/GitHubExtension/Usings.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; diff --git a/test/GitHubExtension/Widgets/Icons.cs b/test/GitHubExtension/Widgets/Icons.cs index bf69ce7a..bc745f1f 100644 --- a/test/GitHubExtension/Widgets/Icons.cs +++ b/test/GitHubExtension/Widgets/Icons.cs @@ -1,8 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using DevHome.Logging; namespace GitHubExtension.Test; + public partial class WidgetTests { [TestMethod] diff --git a/test/GitHubExtension/Widgets/Validation.cs b/test/GitHubExtension/Widgets/Validation.cs index cc0c1523..4f8cc0d9 100644 --- a/test/GitHubExtension/Widgets/Validation.cs +++ b/test/GitHubExtension/Widgets/Validation.cs @@ -1,8 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using DevHome.Logging; namespace GitHubExtension.Test; + public partial class WidgetTests { [TestMethod] diff --git a/test/GitHubExtension/Widgets/WidgetTestsSetup.cs b/test/GitHubExtension/Widgets/WidgetTestsSetup.cs index a5153810..a6eb4c83 100644 --- a/test/GitHubExtension/Widgets/WidgetTestsSetup.cs +++ b/test/GitHubExtension/Widgets/WidgetTestsSetup.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace GitHubExtension.Test; diff --git a/test/UITest/GitHubExtension.UITest.csproj b/test/UITest/GitHubExtension.UITest.csproj index 284309e9..62cfac20 100644 --- a/test/UITest/GitHubExtension.UITest.csproj +++ b/test/UITest/GitHubExtension.UITest.csproj @@ -10,24 +10,19 @@ true resources.pri + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - \ No newline at end of file diff --git a/test/UITest/GitHubExtensionScenarioStandard.cs b/test/UITest/GitHubExtensionScenarioStandard.cs index e45e70f9..3f26704e 100644 --- a/test/UITest/GitHubExtensionScenarioStandard.cs +++ b/test/UITest/GitHubExtensionScenarioStandard.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/UITest/GitHubExtensionSession.cs b/test/UITest/GitHubExtensionSession.cs index 6205732f..c3f83347 100644 --- a/test/UITest/GitHubExtensionSession.cs +++ b/test/UITest/GitHubExtensionSession.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium.Appium;