diff --git a/src/AzureExtension/Strings/en-US/Resources.resw b/src/AzureExtension/Strings/en-US/Resources.resw index c0001dd9..f00b1228 100644 --- a/src/AzureExtension/Strings/en-US/Resources.resw +++ b/src/AzureExtension/Strings/en-US/Resources.resw @@ -138,7 +138,7 @@ Shown in Widget, Tooltip text - You must sign in to view this widget's content. + You must sign in to view this widget's content. Go to the Account Settings page to sign in. Shown in Widget when user is not logged in. diff --git a/src/AzureExtension/Widgets/AzurePullRequestsWidget.cs b/src/AzureExtension/Widgets/AzurePullRequestsWidget.cs index c8576daa..875ecb79 100644 --- a/src/AzureExtension/Widgets/AzurePullRequestsWidget.cs +++ b/src/AzureExtension/Widgets/AzurePullRequestsWidget.cs @@ -22,7 +22,6 @@ internal class AzurePullRequestsWidget : AzureWidget // Widget Data private string widgetTitle = string.Empty; - private string selectedDevId = string.Empty; private string selectedRepositoryUrl = string.Empty; private string selectedRepositoryName = string.Empty; private string selectedView = DefaultSelectedView; @@ -91,19 +90,19 @@ protected override void HandleSubmit(WidgetActionInvokedArgs args) if (dataObject != null && dataObject["account"] != null && dataObject["query"] != null) { widgetTitle = dataObject["widgetTitle"]?.GetValue() ?? string.Empty; - selectedDevId = dataObject["account"]?.GetValue() ?? string.Empty; + DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; selectedRepositoryUrl = dataObject["query"]?.GetValue() ?? string.Empty; selectedView = dataObject["view"]?.GetValue() ?? string.Empty; - SetDefaultDeveloperId(); - if (selectedDevId != dataObject["account"]?.GetValue()) + SetDefaultDeveloperLoginId(); + if (DeveloperLoginId != dataObject["account"]?.GetValue()) { - dataObject["account"] = selectedDevId; + dataObject["account"] = DeveloperLoginId; data = dataObject.ToJsonString(); } ConfigurationData = data; - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { message = Resources.GetResource(@"Widget_Template/DevIDError"); @@ -135,29 +134,21 @@ public override void OnCustomizationRequested(WidgetCustomizationRequestedArgs c SetConfigure(); } - // This method will attempt to select a DeveloperId if one is not already selected. - // It uses the input url and tries to find the most likely DeveloperId that corresponds to the - // url among the set of available DeveloperIds. If there is no best match it chooses the first - // available developerId or none if there are no DeveloperIds. - private void SetDefaultDeveloperId() + // Increase precision of SetDefaultDeveloperLoginId by matching the selectedRepositoryUrl's org + // with the first matching DeveloperId that contains that org. + protected override void SetDefaultDeveloperLoginId() { - if (!string.IsNullOrEmpty(selectedDevId)) - { - return; - } - - var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; - if (devIds is null) - { - return; - } - - // Set as the first DevId found, unless we find a better match from the Url. - selectedDevId = devIds.FirstOrDefault()?.LoginId ?? string.Empty; + base.SetDefaultDeveloperLoginId(); var azureOrg = new AzureUri(selectedRepositoryUrl).Organization; if (!string.IsNullOrEmpty(azureOrg)) { - selectedDevId = devIds.Where(i => i.LoginId.Contains(azureOrg, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.LoginId ?? selectedDevId; + var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; + if (devIds is null) + { + return; + } + + DeveloperLoginId = devIds.Where(i => i.LoginId.Contains(azureOrg, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.LoginId ?? DeveloperLoginId; } } @@ -189,7 +180,7 @@ public override void HandleDataManagerUpdate(object? source, DataManagerUpdateEv public override void RequestContentData() { - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { // Should not happen @@ -219,11 +210,11 @@ private void ResetDataFromState(string data) } widgetTitle = dataObject["widgetTitle"]?.GetValue() ?? string.Empty; - selectedDevId = dataObject["account"]?.GetValue() ?? string.Empty; + DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; selectedRepositoryUrl = dataObject["query"]?.GetValue() ?? string.Empty; selectedView = dataObject["view"]?.GetValue() ?? string.Empty; - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { return; @@ -251,7 +242,7 @@ public override string GetConfiguration(string data) configurationData.Add("accounts", developerIdsData); - configurationData.Add("selectedDevId", selectedDevId); + configurationData.Add("selectedDevId", DeveloperLoginId); configurationData.Add("url", selectedRepositoryUrl); configurationData.Add("selectedView", selectedView); configurationData.Add("message", message); @@ -268,7 +259,7 @@ public override void LoadContentData() { try { - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { // Should not happen diff --git a/src/AzureExtension/Widgets/AzureQueryListWidget.cs b/src/AzureExtension/Widgets/AzureQueryListWidget.cs index df21b6f9..f83e4dfd 100644 --- a/src/AzureExtension/Widgets/AzureQueryListWidget.cs +++ b/src/AzureExtension/Widgets/AzureQueryListWidget.cs @@ -4,7 +4,6 @@ using System.Text.Json.Nodes; using DevHomeAzureExtension.Client; using DevHomeAzureExtension.DataManager; -using DevHomeAzureExtension.DataModel; using DevHomeAzureExtension.DeveloperId; using DevHomeAzureExtension.Helpers; using Microsoft.Windows.Widgets.Providers; @@ -20,7 +19,6 @@ internal class AzureQueryListWidget : AzureWidget // Widget Data private string widgetTitle = string.Empty; - private string selectedDevId = string.Empty; private string selectedQueryUrl = string.Empty; private string selectedQueryId = string.Empty; private string? message; @@ -90,17 +88,17 @@ protected override void HandleSubmit(WidgetActionInvokedArgs args) CanPin = false; widgetTitle = dataObject["widgetTitle"]?.GetValue() ?? string.Empty; selectedQueryUrl = dataObject["query"]?.GetValue() ?? string.Empty; - selectedDevId = dataObject["account"]?.GetValue() ?? string.Empty; - SetDefaultDeveloperId(); - if (selectedDevId != dataObject["account"]?.GetValue()) + DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; + SetDefaultDeveloperLoginId(); + if (DeveloperLoginId != dataObject["account"]?.GetValue()) { - dataObject["account"] = selectedDevId; + dataObject["account"] = DeveloperLoginId; data = dataObject.ToJsonString(); } ConfigurationData = data; - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { message = Resources.GetResource(@"Widget_Template/DevIDError"); @@ -137,29 +135,22 @@ public override void OnCustomizationRequested(WidgetCustomizationRequestedArgs c SetConfigure(); } - // This method will attempt to select a DeveloperId if one is not already selected. - // It uses the input url and tries to find the most likely DeveloperId that corresponds to the - // url among the set of available DeveloperIds. If there is no best match it chooses the first - // available developerId or none if there are no DeveloperIds. - private void SetDefaultDeveloperId() + // Increase precision of SetDefaultDeveloperLoginId by matching the selectedQueryUrl's org + // with the first matching DeveloperId that contains that org. + protected override void SetDefaultDeveloperLoginId() { - if (!string.IsNullOrEmpty(selectedDevId)) - { - return; - } + base.SetDefaultDeveloperLoginId(); - var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; - if (devIds is null) - { - return; - } - - // Set as the first DevId found, unless we find a better match from the Url. - selectedDevId = devIds.FirstOrDefault()?.LoginId ?? string.Empty; var azureOrg = new AzureUri(selectedQueryUrl).Organization; if (!string.IsNullOrEmpty(azureOrg)) { - selectedDevId = devIds.Where(i => i.LoginId.Contains(azureOrg, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.LoginId ?? selectedDevId; + var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; + if (devIds is null) + { + return; + } + + DeveloperLoginId = devIds.Where(i => i.LoginId.Contains(azureOrg, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.LoginId ?? DeveloperLoginId; } } @@ -191,7 +182,7 @@ public override void HandleDataManagerUpdate(object? source, DataManagerUpdateEv public override void RequestContentData() { - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { // Should not happen @@ -222,10 +213,10 @@ private void ResetDataFromState(string data) } widgetTitle = dataObject["widgetTitle"]?.GetValue() ?? string.Empty; - selectedDevId = dataObject["account"]?.GetValue() ?? string.Empty; + DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; selectedQueryUrl = dataObject["query"]?.GetValue() ?? string.Empty; - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { return; @@ -257,7 +248,7 @@ public override string GetConfiguration(string data) configurationData.Add("accounts", developerIdsData); - configurationData.Add("selectedDevId", selectedDevId); + configurationData.Add("selectedDevId", DeveloperLoginId); configurationData.Add("url", selectedQueryUrl); configurationData.Add("message", message); configurationData.Add("widgetTitle", widgetTitle); @@ -273,7 +264,7 @@ public override void LoadContentData() { try { - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { // Should not happen, but may be possible in situations where the app is removed and @@ -293,7 +284,7 @@ public override void LoadContentData() } // This can throw if DataStore is not connected. - Query? queryInfo = DataManager!.GetQuery(azureUri, developerId.LoginId); + var queryInfo = DataManager!.GetQuery(azureUri, developerId.LoginId); var queryResults = queryInfo is null ? new Dictionary() : JsonConvert.DeserializeObject>(queryInfo.QueryResults); diff --git a/src/AzureExtension/Widgets/AzureQueryTilesWidget.cs b/src/AzureExtension/Widgets/AzureQueryTilesWidget.cs index 74e6d7ed..04874437 100644 --- a/src/AzureExtension/Widgets/AzureQueryTilesWidget.cs +++ b/src/AzureExtension/Widgets/AzureQueryTilesWidget.cs @@ -22,8 +22,6 @@ internal class AzureQueryTilesWidget : AzureWidget protected static readonly new string Name = nameof(AzureQueryTilesWidget); private readonly List tiles = new(); - private string selectedDevId = string.Empty; - // Creation and destruction methods public AzureQueryTilesWidget() : base() @@ -185,7 +183,7 @@ public override void HandleDataManagerUpdate(object? source, DataManagerUpdateEv public override void RequestContentData() { - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { // Should not happen @@ -218,7 +216,7 @@ public override void LoadContentData() var data = new JsonObject(); var linesArray = new JsonArray(); - var developerId = GetDevId(selectedDevId); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { // Should not happen @@ -284,8 +282,8 @@ private void ValidateConfigurationData() CanPin = false; var pinIssueFound = false; - SetDefaultDeveloperId(); - var developerId = GetDevId(selectedDevId); + SetDefaultDeveloperLoginId(); + var developerId = GetDevId(DeveloperLoginId); if (developerId == null) { return; @@ -351,29 +349,21 @@ private void ResetNumberOfTilesFromData(string data) } } - // This method will attempt to select a DeveloperId if one is not already selected. - // It uses the url of the first valid tile and tries to find the most likely DeveloperId that - // among the set of available DeveloperIds. If there is no best match it chooses the first - // available developerId or none if there are no DeveloperIds. - private void SetDefaultDeveloperId() + // Increase precision of SetDefaultDeveloperLoginId by matching the first valid org in the + // tiles list with the first matching DeveloperId that contains that org. + protected override void SetDefaultDeveloperLoginId() { - if (!string.IsNullOrEmpty(selectedDevId)) - { - return; - } - - var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; - if (devIds is null) - { - return; - } - - // Set as the first DevId found, unless we find a better match to the first Org found. - selectedDevId = devIds.FirstOrDefault()?.LoginId ?? string.Empty; + base.SetDefaultDeveloperLoginId(); var azureOrg = tiles.Where(i => i.AzureUri.IsValid).FirstOrDefault().AzureUri?.Organization ?? string.Empty; if (!string.IsNullOrEmpty(azureOrg)) { - selectedDevId = devIds.Where(i => i.LoginId.Contains(azureOrg, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.LoginId ?? selectedDevId; + var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; + if (devIds is null) + { + return; + } + + DeveloperLoginId = devIds.Where(i => i.LoginId.Contains(azureOrg, StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.LoginId ?? DeveloperLoginId; } } @@ -407,11 +397,11 @@ private void UpdateAllTiles(string data) } } - selectedDevId = dataObject["account"]?.GetValue() ?? string.Empty; - SetDefaultDeveloperId(); - if (selectedDevId != dataObject["account"]?.GetValue()) + DeveloperLoginId = dataObject["account"]?.GetValue() ?? string.Empty; + SetDefaultDeveloperLoginId(); + if (DeveloperLoginId != dataObject["account"]?.GetValue()) { - dataObject["account"] = selectedDevId; + dataObject["account"] = DeveloperLoginId; data = dataObject.ToJsonString(); } @@ -450,7 +440,7 @@ public override string GetConfiguration(string data) } configurationData.Add("tiles", tilesArray); - configurationData.Add("selectedDevId", selectedDevId); + configurationData.Add("selectedDevId", DeveloperLoginId); configurationData.Add("configuring", !CanPin); configurationData.Add("pinned", Pinned); configurationData.Add("arrow", IconLoader.GetIconAsBase64("arrow.png")); diff --git a/src/AzureExtension/Widgets/AzureWidget.cs b/src/AzureExtension/Widgets/AzureWidget.cs index 5d7a7557..7f13afdc 100644 --- a/src/AzureExtension/Widgets/AzureWidget.cs +++ b/src/AzureExtension/Widgets/AzureWidget.cs @@ -35,6 +35,8 @@ public abstract class AzureWidget : WidgetImpl protected string ContentData { get; set; } = EmptyJson; + protected string DeveloperLoginId { get; set; } = string.Empty; + protected bool CanPin { get; set; @@ -197,10 +199,7 @@ protected async Task HandleSignIn() { Log.Logger()?.ReportInfo(Name, ShortId, $"WidgetAction invoked for user sign in"); - // Do Sign-in Flow - // var authProvider = DeveloperIdProvider.GetInstance(); - UpdateActivityState(); - Log.Logger()?.ReportInfo(Name, ShortId, $"User sign in successful from WidgetAction invocation"); + // We cannot do sign-in flow because it requires the main window of DevHome. } protected WidgetAction GetWidgetActionForVerb(string verb) @@ -230,10 +229,30 @@ public string GetSignIn() return signInData.ToString(); } - public bool IsUserLoggedIn() + protected virtual bool IsUserLoggedIn() { + // User is not logged in if either there are zero DeveloperIds logged in, or the selected + // DeveloperId for this widget is not logged in. var authProvider = DeveloperIdProvider.GetInstance(); - return authProvider.GetLoggedInDeveloperIds().DeveloperIds.Any(); + if (!authProvider.GetLoggedInDeveloperIds().DeveloperIds.Any()) + { + return false; + } + + if (string.IsNullOrEmpty(DeveloperLoginId)) + { + // User has not yet chosen a DeveloperId, but there is at least one available, so the + // user has logged in and we are in a good state. + return true; + } + + if (GetDevId(DeveloperLoginId) is not null) + { + // The selected DeveloperId is logged in so we are in a good state. + return true; + } + + return false; } protected DeveloperId.DeveloperId? GetDevId(string login) @@ -470,8 +489,45 @@ private async Task PeriodicUpdate() lastUpdateRequest = DateTime.Now; } - private void HandleDeveloperIdChange(object? sender, IDeveloperId e) + // This method will attempt to select a DeveloperId if one is not already selected. By default + // this will simply select the first DeveloperId in the list of available DeveloperIds. + protected virtual void SetDefaultDeveloperLoginId() { + if (!string.IsNullOrEmpty(DeveloperLoginId)) + { + return; + } + + var devIds = DeveloperIdProvider.GetInstance().GetLoggedInDeveloperIds().DeveloperIds; + if (devIds is null) + { + return; + } + + // Set as the first DevId found, unless we find a better match from the Url. + DeveloperLoginId = devIds.FirstOrDefault()?.LoginId ?? string.Empty; + } + + protected void HandleDeveloperIdChange(object? sender, IDeveloperId e) + { + if (e.LoginId == DeveloperLoginId) + { + try + { + var state = DeveloperIdProvider.GetInstance().GetDeveloperIdState(e); + if (state == AuthenticationState.LoggedIn) + { + // If the DevId we are set for logs in, refresh the data. + RequestContentData(); + } + } + catch (Exception ex) + { + Log.Logger()?.ReportError(Name, ShortId, $"Failed getting DeveloperId state.", ex); + } + } + + // Update state regardless, since we could be waiting for any developer to sign in. Log.Logger()?.ReportInfo(Name, ShortId, $"Change in Developer Id, Updating widget state."); UpdateActivityState(); } diff --git a/src/AzureExtension/Widgets/Templates/AzureSignInTemplate.json b/src/AzureExtension/Widgets/Templates/AzureSignInTemplate.json index 5bed8ddc..c5293fca 100644 --- a/src/AzureExtension/Widgets/Templates/AzureSignInTemplate.json +++ b/src/AzureExtension/Widgets/Templates/AzureSignInTemplate.json @@ -14,38 +14,6 @@ "height": "stretch", "size": "Default", "weight": "Bolder" - }, - { - "type": "ColumnSet", - "spacing": "ExtraLarge", - "columns": [ - { - "type": "Column", - "width": "stretch" - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "ActionSet", - "actions": [ - { - "type": "Action.Execute", - "title": "%Widget_Template_Button/SignIn%", - "verb": "SignIn", - "tooltip": "%Widget_Template_Tooltip/SignIn%" - } - ], - "spacing": "ExtraLarge" - } - ] - }, - { - "type": "Column", - "width": "stretch" - } - ] } ], "horizontalAlignment": "Center",