Skip to content

Enable/disable sign in button #201

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions GitHubExtension.Test/Controls/SignInFormTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using GitHubExtension.DeveloperId;
using Moq;
using Octokit;

namespace GitHubExtension.Test.Controls;

[TestClass]
public class SignInFormTest
{
[TestMethod]
public async Task HandleOAuthRedirection_ShouldThrowInvalidOperationException_OnTimeout()
{
var mockGitHubClient = new Mock<IGitHubClient>();
var mockOauthClient = new Mock<IOauthClient>();

mockGitHubClient.Setup(client => client.Oauth).Returns(mockOauthClient.Object);

mockOauthClient
.Setup(oauth => oauth.CreateAccessToken(It.IsAny<OauthTokenRequest>()))
.Returns(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(10)); // Simulate delay longer than timeout
return new OauthToken();
});

var developerIdProvider = new DeveloperIdProvider();

mockGitHubClient.Setup(client => client.Oauth).Returns(mockOauthClient.Object);

mockOauthClient
.Setup(oauth => oauth.CreateAccessToken(It.IsAny<OauthTokenRequest>()))
.Returns(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(10));
return new OauthToken();
});

var oAuthRequest = new OAuthRequest();

var authorizationResponse = new Uri("https://example.com/callback?code=valid_code");

await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () =>
{
await Task.Run(() => developerIdProvider.HandleOauthRedirection(authorizationResponse));
});
}
}
39 changes: 39 additions & 0 deletions GitHubExtension.Test/DeveloperIdTests/OAuthRequestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using GitHubExtension.DeveloperId;
using Moq;
using Octokit;

namespace GitHubExtension.Test.DeveloperIdTests;

[TestClass]
public class OAuthRequestTest
{
[TestMethod]
public async Task CompleteOAuthAsync_ShouldThrowInvalidOperationException_OnTimeout()
{
var mockGitHubClient = new Mock<IGitHubClient>();
var mockOauthClient = new Mock<IOauthClient>();

mockGitHubClient.Setup(client => client.Oauth).Returns(mockOauthClient.Object);

mockOauthClient
.Setup(oauth => oauth.CreateAccessToken(It.IsAny<OauthTokenRequest>()))
.Returns(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(10));
return new OauthToken();
});

var oAuthRequest = new OAuthRequest();

var authorizationResponse = new Uri("https://example.com/callback?code=valid_code");

await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () =>
{
await oAuthRequest.CompleteOAuthAsync(authorizationResponse);
});
}
}
37 changes: 36 additions & 1 deletion GitHubExtension/Controls/Forms/SignInForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using GitHubExtension.DeveloperId;
using GitHubExtension.Helpers;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;

namespace GitHubExtension.Controls.Forms;

Expand All @@ -21,10 +21,43 @@ public partial class SignInForm : FormContent, IGitHubForm
private readonly IDeveloperIdProvider _developerIdProvider;
private readonly IResources _resources;

private bool _isButtonEnabled = true;

private string IsButtonEnabled =>
_isButtonEnabled.ToString(CultureInfo.InvariantCulture).ToLower(CultureInfo.InvariantCulture);

public SignInForm(IDeveloperIdProvider developerIdProvider, IResources resources)
{
_resources = resources;
_developerIdProvider = developerIdProvider;
_developerIdProvider.OAuthRedirected += DeveloperIdProvider_OAuthRedirected;
SignOutForm.SignOutAction += SignOutForm_SignOutAction;
}

private void SignOutForm_SignOutAction(object? sender, SignInStatusChangedEventArgs e)
{
_isButtonEnabled = !e.IsSignedIn;
}

private void DeveloperIdProvider_OAuthRedirected(object? sender, Exception? e)
{
if (e is not null)
{
SetButtonEnabled(true);
LoadingStateChanged?.Invoke(this, false);
SignInAction?.Invoke(this, new SignInStatusChangedEventArgs(false, e));
FormSubmitted?.Invoke(this, new FormSubmitEventArgs(false, e));
return;
}

SetButtonEnabled(false);
}

private void SetButtonEnabled(bool isEnabled)
{
_isButtonEnabled = isEnabled;
TemplateJson = TemplateHelper.LoadTemplateJsonFromTemplateName("AuthTemplate", TemplateSubstitutions);
OnPropertyChanged(nameof(TemplateJson));
}

public Dictionary<string, string> TemplateSubstitutions => new()
Expand All @@ -33,6 +66,7 @@ public SignInForm(IDeveloperIdProvider developerIdProvider, IResources resources
{ "{{AuthButtonTitle}}", _resources.GetResource("Forms_Sign_In") },
{ "{{AuthIcon}}", $"data:image/png;base64,{GitHubIcon.GetBase64Icon("logo")}" },
{ "{{AuthButtonTooltip}}", _resources.GetResource("Forms_Sign_In_Tooltip") },
{ "{{ButtonIsEnabled}}", IsButtonEnabled },
};

public override string TemplateJson => TemplateHelper.LoadTemplateJsonFromTemplateName("AuthTemplate", TemplateSubstitutions);
Expand All @@ -52,6 +86,7 @@ public override ICommandResult SubmitForm(string inputs, string data)
catch (Exception ex)
{
LoadingStateChanged?.Invoke(this, false);
SetButtonEnabled(true);
SignInAction?.Invoke(this, new SignInStatusChangedEventArgs(false, ex));
FormSubmitted?.Invoke(this, new FormSubmitEventArgs(false, ex));
}
Expand Down
1 change: 1 addition & 0 deletions GitHubExtension/Controls/Forms/SignOutForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public SignOutForm(IDeveloperIdProvider developerIdProvider, IResources resource
{ "{{AuthButtonTitle}}", _resources.GetResource("Forms_Sign_Out_Button_Title") },
{ "{{AuthIcon}}", $"data:image/png;base64,{GitHubIcon.GetBase64Icon("logo")}" },
{ "{{AuthButtonTooltip}}", _resources.GetResource("Forms_Sign_Out_Tooltip") },
{ "{{ButtonIsEnabled}}", "true" },
};

public override string TemplateJson => TemplateHelper.LoadTemplateJsonFromTemplateName("AuthTemplate", TemplateSubstitutions);
Expand Down
22 changes: 15 additions & 7 deletions GitHubExtension/Controls/Pages/SignInPage.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using GitHubExtension.Controls.Forms;
using GitHubExtension.Helpers;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;

namespace GitHubExtension.Controls.Pages;

Expand All @@ -24,12 +25,19 @@ public SignInPage(SignInForm signInForm, StatusMessage statusMessage, string suc
_errorMessage = errorMessage;

// Wire up events using the helper
FormEventHelper.WireFormEvents(_signInForm, this, _statusMessage, _successMessage, _errorMessage);
FormEventHelper.WireFormEvents(_signInForm, this, _statusMessage, _successMessage, _errorMessage);

_signInForm.PropChanged += UpdatePage;

// Hide status message initially
ExtensionHost.HideStatus(_statusMessage);
}

}

private void UpdatePage(object sender, IPropChangedEventArgs args)
{
RaiseItemsChanged();
}

public override IContent[] GetContent()
{
ExtensionHost.HideStatus(_statusMessage);
Expand Down
7 changes: 4 additions & 3 deletions GitHubExtension/Controls/Templates/AuthTemplate.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"version": "1.6",
"body": [
{
"type": "Container",
Expand Down Expand Up @@ -37,9 +37,10 @@
"type": "ActionSet",
"actions": [
{
"type": "Action.Submit",
"title": "{{AuthButtonTitle}}",
"tooltip": "{{AuthButtonTooltip}}"
"tooltip": "{{AuthButtonTooltip}}",
"type": "Action.Submit",
"isEnabled": {{ButtonIsEnabled}}
}
]
}
Expand Down
20 changes: 16 additions & 4 deletions GitHubExtension/DeveloperId/DeveloperIdProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ private List<DeveloperId> DeveloperIds
get; set;
}

private readonly Lazy<CredentialVault> _credentialVault;
private readonly Lazy<CredentialVault> _credentialVault;

public event EventHandler<Exception?>? OAuthRedirected;

// Private constructor for Singleton class.
public DeveloperIdProvider()
Expand Down Expand Up @@ -147,7 +149,9 @@ public bool LogoutDeveloperId(IDeveloperId developerId)

public void HandleOauthRedirection(Uri authorizationResponse)
{
OAuthRequest? oAuthRequest = null;
OAuthRequest? oAuthRequest = null;

OAuthRedirected?.Invoke(this, null);

lock (_oAuthRequestsLock)
{
Expand Down Expand Up @@ -180,8 +184,16 @@ public void HandleOauthRedirection(Uri authorizationResponse)
OAuthRequests.Remove(oAuthRequest);
}
}

oAuthRequest.CompleteOAuthAsync(authorizationResponse).Wait();

try
{
oAuthRequest.CompleteOAuthAsync(authorizationResponse).Wait();
}
catch (Exception ex)
{
_log.Error(ex, $"Error while completing OAuth request: ");
OAuthRedirected?.Invoke(this, ex);
}
}

public IEnumerable<IDeveloperId> GetLoggedInDeveloperIdsInternal()
Expand Down
4 changes: 3 additions & 1 deletion GitHubExtension/DeveloperId/IDeveloperIdProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ public interface IDeveloperIdProvider

bool LogoutDeveloperId(IDeveloperId developerId);

void HandleOauthRedirection(Uri authorizationResponse);
void HandleOauthRedirection(Uri authorizationResponse);

public event EventHandler<Exception?>? OAuthRedirected;
}
Loading