From 4b264c5a24fe9b12768c8d3f6f12743cc693a9bb Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Fri, 5 May 2017 22:54:44 +0200 Subject: [PATCH 1/9] Moving forward to a using a flow for the user. --- .../App_Start/Bootstrap.cs | 3 +- .../Controllers/MessagesController.cs | 8 +-- .../Dialogs/ConnectDialog.cs | 9 ++-- .../Dialogs/RootDialog.cs | 49 +++++++++++++++++-- .../AttachmentCollectionExtensions.cs | 15 ++++++ .../Extensions/BotDataBagExtensions.cs | 26 +++++++--- .../DependencyResolverExtensions.cs | 2 +- .../Resources/Labels.Designer.cs | 45 +++++++++++++++++ .../Resources/Labels.resx | 17 +++++++ .../Team-Services-Bot.Api.csproj | 1 + .../WebApiApplication.asax.cs | 8 +-- 11 files changed, 157 insertions(+), 26 deletions(-) diff --git a/src/Team-Services-Bot.Api/App_Start/Bootstrap.cs b/src/Team-Services-Bot.Api/App_Start/Bootstrap.cs index 8cb04ec..ce3b834 100644 --- a/src/Team-Services-Bot.Api/App_Start/Bootstrap.cs +++ b/src/Team-Services-Bot.Api/App_Start/Bootstrap.cs @@ -16,7 +16,6 @@ namespace Vsar.TSBot using Autofac.Extras.AttributeMetadata; using Autofac.Integration.Mvc; using Autofac.Integration.WebApi; - using DI; using Dialogs; using Microsoft.ApplicationInsights; using Microsoft.Bot.Builder.Dialogs; @@ -33,7 +32,7 @@ public static class Bootstrap /// Container builder to be used. /// Flag that indicates if the application is in debugging modus. /// A . - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Reviewed.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Bootstrapper for Autofac. So it is intented to hit all needed dependencies in one place.")] public static IContainer Build(ContainerBuilder builder, bool isDebugging) { if (builder == null) diff --git a/src/Team-Services-Bot.Api/Controllers/MessagesController.cs b/src/Team-Services-Bot.Api/Controllers/MessagesController.cs index f476678..0856f0c 100644 --- a/src/Team-Services-Bot.Api/Controllers/MessagesController.cs +++ b/src/Team-Services-Bot.Api/Controllers/MessagesController.cs @@ -18,7 +18,6 @@ namespace Vsar.TSBot using DI; using Dialogs; using Microsoft.ApplicationInsights; - using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; /// @@ -52,10 +51,11 @@ public async Task Post([FromBody] Activity activity) try { - if (string.Compare(activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) || + string.Equals(activity.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase)) { - IDialogInvoker invoker = this.container.Resolve(); - RootDialog dialog = this.container.Resolve(); + var invoker = this.container.Resolve(); + var dialog = this.container.Resolve(); await invoker.SendAsync(activity, () => dialog); } else diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index bc1843b..b0ffaa3 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -26,11 +26,10 @@ namespace Vsar.TSBot.Dialogs public class ConnectDialog : IDialog { private const string CommandMatch = "connect (.*?)$"; - private const string Scope = "vso.agentpools_manage%20vso.build_execute%20vso.chat_manage%20vso.code_manage%20vso.code_status%20" + - "vso.connected_server%20vso.dashboards_manage%20vso.entitlements%20vso.extension.data_write%20" + - "vso.extension_manage%20vso.gallery_acquire%20vso.gallery_manage%20vso.identity%20vso.loadtest_write%20" + - "vso.notification_manage%20vso.packaging_manage%20vso.profile_write%20vso.project_manage%20" + - "vso.release_manage%20vso.security_manage%20vso.serviceendpoint_manage%20vso.test_write%20vso.work_write"; + private const string Scope = "vso.agentpools%20vso.build_execute%20vso.chat_write%20vso.code%20vso.connected_server%20" + + "vso.dashboards%20vso.entitlements%20vso.extension%20vso.extension.data%20vso.gallery%20" + + "vso.identity%20vso.loadtest%20vso.notification%20vso.packaging%20vso.project%20" + + "vso.release_execute%20vso.serviceendpoint_query%20vso.taskgroups%20vso.test%20vso.work"; private const string UrlOAuth = "https://app.vssps.visualstudio.com/oauth2/authorize?client_id={0}&response_type=Assertion&state={1};{2}&scope={3}&redirect_uri={4}"; diff --git a/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs b/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs index dda4860..5b42fd1 100644 --- a/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs @@ -10,12 +10,15 @@ namespace Vsar.TSBot.Dialogs { using System; + using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web.Http; + using Cards; using Microsoft.ApplicationInsights; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; + using Resources; /// /// Represents the Root dialog from where all conversations start. @@ -34,13 +37,28 @@ public Task StartAsync(IDialogContext context) private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { var activity = await result; - var dialog = GlobalConfiguration.Configuration.DependencyResolver.Find(activity.Text); var telemetryClient = GlobalConfiguration.Configuration.DependencyResolver.GetService(); + // Occurs when the conversation starts. + if (string.Equals(activity.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase)) + { + await this.Welcome(context, activity); + } + + await this.ProcessCommand(context, activity, telemetryClient); + } + + private async Task ProcessCommand(IDialogContext context, IMessageActivity activity, TelemetryClient telemetryClient) + { + var dialog = GlobalConfiguration.Configuration.DependencyResolver.Find(activity.Text); + if (dialog == null) { - // TODO: Forward to the help dialog. - await context.PostAsync("Unknown command."); + var reply = context.MakeMessage(); + + reply.Attachments.Add(new MainOptionsCard()); + + context.Wait(this.MessageReceivedAsync); } else { @@ -50,8 +68,33 @@ private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { + context.Wait(this.MessageReceivedAsync); return Task.CompletedTask; } } diff --git a/src/Team-Services-Bot.Api/Extensions/AttachmentCollectionExtensions.cs b/src/Team-Services-Bot.Api/Extensions/AttachmentCollectionExtensions.cs index 581b6fb..c04907a 100644 --- a/src/Team-Services-Bot.Api/Extensions/AttachmentCollectionExtensions.cs +++ b/src/Team-Services-Bot.Api/Extensions/AttachmentCollectionExtensions.cs @@ -18,6 +18,21 @@ namespace Vsar.TSBot /// public static class AttachmentCollectionExtensions { + /// + /// Adds and converts a card to the attachments list. + /// + /// A list of attachments. + /// The card. + public static void Add(this IList attachments, HeroCard card) + { + if (attachments == null) + { + throw new ArgumentNullException(nameof(attachments)); + } + + attachments.Add(card.ToAttachment()); + } + /// /// Adds and converts a card to the attachments list. /// diff --git a/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs b/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs index c14321c..5df5d2f 100644 --- a/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs +++ b/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs @@ -22,6 +22,7 @@ public static class BotDataBagExtensions private const string Account = "Account"; private const string Profile = "Profile"; private const string Profiles = "Profiles"; + private const string TeamProject = "TeamProject"; /// /// Gets the current account name. @@ -30,14 +31,27 @@ public static class BotDataBagExtensions /// A string representing the account. public static string GetCurrentAccount(this IBotDataBag dataBag) { - string result; + if (dataBag == null) + { + throw new ArgumentNullException(nameof(dataBag)); + } + + return dataBag.TryGetValue(Account, out string result) ? result : string.Empty; + } + /// + /// Gets the current team project for the user. + /// + /// The . + /// the name of the current team project. + public static string GetCurrentTeamProject(this IBotDataBag dataBag) + { if (dataBag == null) { throw new ArgumentNullException(nameof(dataBag)); } - return dataBag.TryGetValue(Account, out result) ? result : string.Empty; + return dataBag.TryGetValue(TeamProject, out string teamProject) ? teamProject : string.Empty; } /// @@ -47,14 +61,12 @@ public static string GetCurrentAccount(this IBotDataBag dataBag) /// A VstsProfile. public static VstsProfile GetProfile(this IBotDataBag dataBag) { - VstsProfile profile; - if (dataBag == null) { throw new ArgumentNullException(nameof(dataBag)); } - return dataBag.TryGetValue(Profile, out profile) ? profile : null; + return dataBag.TryGetValue(Profile, out VstsProfile profile) ? profile : null; } /// @@ -79,14 +91,12 @@ public static IList GetProfiles(this BotData data) /// A list of profiles. public static IList GetProfiles(this IBotDataBag dataBag) { - IList results; - if (dataBag == null) { throw new ArgumentNullException(nameof(dataBag)); } - return dataBag.TryGetValue(Profiles, out results) ? results : new List(); + return dataBag.TryGetValue(Profiles, out IList results) ? results : new List(); } /// diff --git a/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs b/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs index f79956c..9135cb6 100644 --- a/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs +++ b/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs @@ -38,7 +38,7 @@ public static IDialog Find(this IDependencyScope resolver, string activi var dialogs = resolver.GetServices>>(); return dialogs - .Where(m => activityText.StartsWith(m.Metadata["Command"].ToString(), StringComparison.OrdinalIgnoreCase)) + .Where(m => activityText.Trim().StartsWith(m.Metadata["Command"].ToString(), StringComparison.OrdinalIgnoreCase)) .Select(m => m.Value) .FirstOrDefault(); } diff --git a/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs b/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs index d9f5c71..610f389 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs +++ b/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs @@ -60,6 +60,15 @@ internal class Labels { } } + /// + /// Looks up a localized string similar to Approvals. + /// + internal static string Approvals { + get { + return ResourceManager.GetString("Approvals", resourceCulture); + } + } + /// /// Looks up a localized string similar to Authentication is required.. /// @@ -86,5 +95,41 @@ internal class Labels { return ResourceManager.GetString("PleaseLogin", resourceCulture); } } + + /// + /// Looks up a localized string similar to Queue Build. + /// + internal static string QueueBuild { + get { + return ResourceManager.GetString("QueueBuild", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to QueueRelease. + /// + internal static string QueueRelease { + get { + return ResourceManager.GetString("QueueRelease", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Welcome back {0}. I have connected you to Account '{1}', Team Project '{2}'.. + /// + internal static string WelcomeExistingUser { + get { + return ResourceManager.GetString("WelcomeExistingUser", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Welcome {0}. I see this is the first team we speak.. + /// + internal static string WelcomeNewUser { + get { + return ResourceManager.GetString("WelcomeNewUser", resourceCulture); + } + } } } diff --git a/src/Team-Services-Bot.Api/Resources/Labels.resx b/src/Team-Services-Bot.Api/Resources/Labels.resx index 4addbf4..9c9a5bb 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.resx +++ b/src/Team-Services-Bot.Api/Resources/Labels.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Approvals + Authentication is required. @@ -127,4 +130,18 @@ Please login to Team Services. + + Queue Build + + + QueueRelease + + + Welcome back {0}. I have connected you to Account '{1}', Team Project '{2}'. + {0} = from name; {1] = account name; {2} = team project name; + + + Welcome {0}. I see this is the first team we speak. + {0} = from name + \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj index 4e58b10..433875c 100644 --- a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj +++ b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj @@ -261,6 +261,7 @@ + diff --git a/src/Team-Services-Bot.Api/WebApiApplication.asax.cs b/src/Team-Services-Bot.Api/WebApiApplication.asax.cs index 26bb2c7..8e9a260 100644 --- a/src/Team-Services-Bot.Api/WebApiApplication.asax.cs +++ b/src/Team-Services-Bot.Api/WebApiApplication.asax.cs @@ -26,14 +26,16 @@ public class WebApiApplication : System.Web.HttpApplication /// /// Method is called when the application starts. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Reviewed.")] protected void Application_Start() { TelemetryConfiguration.Active.InstrumentationKey = WebConfigurationManager.AppSettings["InstrumentationKey"]; // Web API configuration and services - ContainerBuilder builder = new ContainerBuilder(); - builder.RegisterType().As(); + var builder = new ContainerBuilder(); + builder + .RegisterType() + .As(); + var container = Bootstrap.Build(builder, this.Context.IsDebuggingEnabled); GlobalConfiguration.Configure(c => WebApiConfig.Register(c, container)); From 52e8ef4715f4aa13517ac5d57423b57689962e9e Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sat, 6 May 2017 08:20:40 +0200 Subject: [PATCH 2/9] More changes to the flow. --- .../Cards/AccountCard.cs | 30 +++++ src/Team-Services-Bot.Api/Cards/LoginCard.cs | 49 ++++++++ .../Cards/MainOptionsCard.cs | 33 ++++++ .../Dialogs/ConnectDialog.cs | 106 +++++++++++------- .../Dialogs/RootDialog.cs | 7 +- .../Extensions/BotDataBagExtensions.cs | 35 ++++++ .../DependencyResolverExtensions.cs | 5 + .../Resources/Labels.Designer.cs | 9 ++ .../Resources/Labels.resx | 3 + .../Team-Services-Bot.Api.csproj | 2 + 10 files changed, 231 insertions(+), 48 deletions(-) create mode 100644 src/Team-Services-Bot.Api/Cards/AccountCard.cs create mode 100644 src/Team-Services-Bot.Api/Cards/LoginCard.cs create mode 100644 src/Team-Services-Bot.Api/Cards/MainOptionsCard.cs diff --git a/src/Team-Services-Bot.Api/Cards/AccountCard.cs b/src/Team-Services-Bot.Api/Cards/AccountCard.cs new file mode 100644 index 0000000..fdd391f --- /dev/null +++ b/src/Team-Services-Bot.Api/Cards/AccountCard.cs @@ -0,0 +1,30 @@ +// ——————————————————————————————— +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// +// Contains the account from wich the user can choose. +// +// ——————————————————————————————— + +namespace Vsar.TSBot.Cards +{ + using System.Collections.Generic; + using Microsoft.Bot.Connector; + + /// + /// Represents the Account Card. + /// + public class AccountCard : HeroCard + { + /// + /// Initializes a new instance of the class. + /// + /// The account. + public AccountCard(string account) + { + var button = new CardAction(ActionTypes.ImBack, account, value: $"connect {account}"); + this.Buttons = new List { button }; + } + } +} \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Cards/LoginCard.cs b/src/Team-Services-Bot.Api/Cards/LoginCard.cs new file mode 100644 index 0000000..f90cfff --- /dev/null +++ b/src/Team-Services-Bot.Api/Cards/LoginCard.cs @@ -0,0 +1,49 @@ +// ——————————————————————————————— +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// +// Contains the login card. +// +// ——————————————————————————————— + +namespace Vsar.TSBot.Cards +{ + using System.Collections.Generic; + using Microsoft.Bot.Connector; + using Resources; + + /// + /// Represent the login card. + /// + public class LoginCard : SigninCard + { + private const string Scope = "vso.agentpools%20vso.build_execute%20vso.chat_write%20vso.code%20vso.connected_server%20" + + "vso.dashboards%20vso.entitlements%20vso.extension%20vso.extension.data%20vso.gallery%20" + + "vso.identity%20vso.loadtest%20vso.notification%20vso.packaging%20vso.project%20" + + "vso.release_execute%20vso.serviceendpoint_query%20vso.taskgroups%20vso.test%20vso.work"; + + private const string UrlOAuth = "https://app.vssps.visualstudio.com/oauth2/authorize?client_id={0}&response_type=Assertion&state={1};{2}&scope={3}&redirect_uri={4}"; + + /// + /// Initializes a new instance of the class. + /// + /// The app id. + /// The authorizeUrl. + /// The channelId. + /// The text on the card. + /// The userId. + public LoginCard(string appId, string authorizeUrl, string channelId, string text, string userId) + : base(text) + { + var button = new CardAction + { + Value = string.Format(UrlOAuth, appId, channelId, userId, Scope, authorizeUrl), + Type = ActionTypes.Signin, + Title = Labels.AuthenticationRequired + }; + + this.Buttons = new List { button }; + } + } +} \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Cards/MainOptionsCard.cs b/src/Team-Services-Bot.Api/Cards/MainOptionsCard.cs new file mode 100644 index 0000000..613d679 --- /dev/null +++ b/src/Team-Services-Bot.Api/Cards/MainOptionsCard.cs @@ -0,0 +1,33 @@ +// ——————————————————————————————— +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// +// Contains the main options. +// +// ——————————————————————————————— + +namespace Vsar.TSBot.Cards +{ + using System.Collections.Generic; + using Microsoft.Bot.Connector; + using Resources; + + /// + /// Contains the main options. + /// + public class MainOptionsCard : HeroCard + { + /// + /// Initializes a new instance of the class. + /// + public MainOptionsCard() + { + var button1 = new CardAction(ActionTypes.ImBack, Labels.Approvals, value: "approvals"); + var button2 = new CardAction(ActionTypes.ImBack, Labels.QueueBuild, value: "build queue"); + var button3 = new CardAction(ActionTypes.ImBack, Labels.QueueRelease, value: "release queue"); + + this.Buttons = new List { button1, button2, button3 }; + } + } +} \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index b0ffaa3..b946236 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -14,6 +14,7 @@ namespace Vsar.TSBot.Dialogs using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; + using Cards; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; using Resources; @@ -25,13 +26,7 @@ namespace Vsar.TSBot.Dialogs [Serializable] public class ConnectDialog : IDialog { - private const string CommandMatch = "connect (.*?)$"; - private const string Scope = "vso.agentpools%20vso.build_execute%20vso.chat_write%20vso.code%20vso.connected_server%20" + - "vso.dashboards%20vso.entitlements%20vso.extension%20vso.extension.data%20vso.gallery%20" + - "vso.identity%20vso.loadtest%20vso.notification%20vso.packaging%20vso.project%20" + - "vso.release_execute%20vso.serviceendpoint_query%20vso.taskgroups%20vso.test%20vso.work"; - - private const string UrlOAuth = "https://app.vssps.visualstudio.com/oauth2/authorize?client_id={0}&response_type=Assertion&state={1};{2}&scope={3}&redirect_uri={4}"; + private const string CommandMatch = "(connect)? *(\\w*) *(\\w*)"; private readonly string appId; private readonly string authorizeUrl; @@ -63,46 +58,71 @@ public Task StartAsync(IDialogContext context) private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { var activity = await result; + var profile = context.UserData.GetCurrentProfile(); + var profiles = context.UserData.GetProfiles(); + var reply = context.MakeMessage(); var match = Regex.Match(activity.Text.ToLowerInvariant(), CommandMatch); - if (match.Success) + if (!match.Success) + { + return; + } + + var account = match.Groups[1].Value; + var teamProject = match.Groups[2].Value; + + if (!profiles.Any()) + { + await this.Login(context, activity, reply); + } + + if (string.IsNullOrWhiteSpace(account)) + { + await this.SelectAccount(context, profiles, reply); + } + + context.UserData.SetCurrentAccount(account); + + if (string.IsNullOrWhiteSpace(teamProject)) + { + // Select Team Project. + } + + context.UserData.SetCurrentTeamProject(teamProject); + + if (profile != null) { - var account = match.Groups[1].Value; - context.UserData.SetCurrentAccount(account); - - var reply = context.MakeMessage(); - var profiles = context.UserData.GetProfiles(); - var profile = profiles.FirstOrDefault(p => p.Accounts.Any(a => a.Equals(account, StringComparison.OrdinalIgnoreCase))); - - if (profile != null) - { - context.UserData.SetCurrentProfile(profile); - reply.Text = string.Format(Labels.ConnectedTo, account); - } - else - { - var button = new CardAction - { - Value = string.Format( - UrlOAuth, - this.appId, - activity.ChannelId, - activity.From.Id, - Scope, - this.authorizeUrl), - Type = activity.IsTeamsChannel() ? "openUrl" : "signin", - Title = Labels.AuthenticationRequired - }; - - var card = new SigninCard(Labels.PleaseLogin, new List { button }); - - reply.Attachments.Add(card); - } - - await context.PostAsync(reply); - - context.Done(reply); + context.UserData.SetCurrentProfile(profile); + reply.Text = string.Format(Labels.ConnectedTo, account); } + + await context.PostAsync(reply); + + context.Done(reply); + } + + private async Task Login(IDialogContext context, IMessageActivity activity, IMessageActivity reply) + { + var card = new LoginCard(this.appId, this.authorizeUrl, activity.ChannelId, Labels.PleaseLogin, activity.From.Id); + + reply.Attachments.Add(card); + + await context.PostAsync(reply); + context.Wait(this.MessageReceivedAsync); + } + + private async Task SelectAccount(IDialogContext context, IList profiles, IMessageActivity reply) + { + reply.Text = Labels.ConnectToAccount; + foreach (var acc in profiles.SelectMany(a => a.Accounts).Distinct().OrderBy(a => a)) + { + reply.Attachments.Add(new AccountCard(acc)); + } + + reply.AttachmentLayout = AttachmentLayoutTypes.Carousel; + + await context.PostAsync(reply); + context.Wait(this.MessageReceivedAsync); } } } \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs b/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs index 5b42fd1..5146b83 100644 --- a/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs @@ -80,11 +80,8 @@ private async Task Welcome(IDialogContext context, IMessageActivity activity) { await context.PostAsync(string.Format(Labels.WelcomeNewUser, activity.From.Name)); - var forward = context.MakeMessage(); - forward.Text = $"connect {account}"; - - var dialog = GlobalConfiguration.Configuration.DependencyResolver.Find(forward.Text); - await context.Forward(dialog, this.ResumeAfterChildDialog, forward, CancellationToken.None); + var dialog = GlobalConfiguration.Configuration.DependencyResolver.Find("connect"); + context.Call(dialog, this.ResumeAfterChildDialog); } else { diff --git a/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs b/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs index 5df5d2f..878e954 100644 --- a/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs +++ b/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs @@ -39,6 +39,21 @@ public static string GetCurrentAccount(this IBotDataBag dataBag) return dataBag.TryGetValue(Account, out string result) ? result : string.Empty; } + /// + /// Gets the current profile. + /// + /// The . + /// A string representing the account. + public static VstsProfile GetCurrentProfile(this IBotDataBag dataBag) + { + if (dataBag == null) + { + throw new ArgumentNullException(nameof(dataBag)); + } + + return dataBag.TryGetValue(Profile, out VstsProfile profile) ? profile : null; + } + /// /// Gets the current team project for the user. /// @@ -159,6 +174,26 @@ public static void SetCurrentProfile(this IBotDataBag dataBag, VstsProfile profi dataBag.SetValue(Profile, profile); } + /// + /// Sets the current team project. + /// + /// The data bag. + /// The team project. + public static void SetCurrentTeamProject(this IBotDataBag dataBag, string teamProject) + { + if (dataBag == null) + { + throw new ArgumentNullException(nameof(dataBag)); + } + + if (string.IsNullOrWhiteSpace(teamProject)) + { + throw new ArgumentNullException(nameof(teamProject)); + } + + dataBag.SetValue(TeamProject, teamProject); + } + /// /// Sets the profile. /// diff --git a/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs b/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs index 9135cb6..95df567 100644 --- a/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs +++ b/src/Team-Services-Bot.Api/Extensions/DependencyResolverExtensions.cs @@ -35,6 +35,11 @@ public static IDialog Find(this IDependencyScope resolver, string activi throw new ArgumentNullException(nameof(resolver)); } + if (string.IsNullOrWhiteSpace(activityText)) + { + return null; + } + var dialogs = resolver.GetServices>>(); return dialogs diff --git a/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs b/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs index 610f389..e68f0a0 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs +++ b/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs @@ -87,6 +87,15 @@ internal class Labels { } } + /// + /// Looks up a localized string similar to Please select an account to connect to.. + /// + internal static string ConnectToAccount { + get { + return ResourceManager.GetString("ConnectToAccount", resourceCulture); + } + } + /// /// Looks up a localized string similar to Please login to Team Services.. /// diff --git a/src/Team-Services-Bot.Api/Resources/Labels.resx b/src/Team-Services-Bot.Api/Resources/Labels.resx index 9c9a5bb..a8c8748 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.resx +++ b/src/Team-Services-Bot.Api/Resources/Labels.resx @@ -127,6 +127,9 @@ Connected to {0}. {0} = accountname; + + Please select an account to connect to. + Please login to Team Services. diff --git a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj index 433875c..7b4036a 100644 --- a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj +++ b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj @@ -261,6 +261,8 @@ + + From ba3981da1a33178b0fbf11fdc0877a284c7a424d Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sat, 6 May 2017 17:26:25 +0200 Subject: [PATCH 3/9] Working on the flow. --- src/Team-Services-Bot.Api/Cards/LoginCard.cs | 2 +- .../Controllers/MessagesController.cs | 3 +-- src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs | 12 ++++++------ src/Team-Services-Bot.Api/Dialogs/RootDialog.cs | 13 +++++++++---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Team-Services-Bot.Api/Cards/LoginCard.cs b/src/Team-Services-Bot.Api/Cards/LoginCard.cs index f90cfff..6138d40 100644 --- a/src/Team-Services-Bot.Api/Cards/LoginCard.cs +++ b/src/Team-Services-Bot.Api/Cards/LoginCard.cs @@ -21,7 +21,7 @@ public class LoginCard : SigninCard private const string Scope = "vso.agentpools%20vso.build_execute%20vso.chat_write%20vso.code%20vso.connected_server%20" + "vso.dashboards%20vso.entitlements%20vso.extension%20vso.extension.data%20vso.gallery%20" + "vso.identity%20vso.loadtest%20vso.notification%20vso.packaging%20vso.project%20" + - "vso.release_execute%20vso.serviceendpoint_query%20vso.taskgroups%20vso.test%20vso.work"; + "vso.release_execute%20vso.serviceendpoint%20vso.taskgroups%20vso.test%20vso.work"; private const string UrlOAuth = "https://app.vssps.visualstudio.com/oauth2/authorize?client_id={0}&response_type=Assertion&state={1};{2}&scope={3}&redirect_uri={4}"; diff --git a/src/Team-Services-Bot.Api/Controllers/MessagesController.cs b/src/Team-Services-Bot.Api/Controllers/MessagesController.cs index 0856f0c..e6a6598 100644 --- a/src/Team-Services-Bot.Api/Controllers/MessagesController.cs +++ b/src/Team-Services-Bot.Api/Controllers/MessagesController.cs @@ -51,8 +51,7 @@ public async Task Post([FromBody] Activity activity) try { - if (string.Equals(activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) || - string.Equals(activity.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase)) { var invoker = this.container.Resolve(); var dialog = this.container.Resolve(); diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index b946236..397f4e2 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -68,17 +68,19 @@ private async Task MessageReceivedAsync(IDialogContext context, IAwaitable prof reply.Attachments.Add(new AccountCard(acc)); } - reply.AttachmentLayout = AttachmentLayoutTypes.Carousel; - await context.PostAsync(reply); context.Wait(this.MessageReceivedAsync); } diff --git a/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs b/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs index 5146b83..222394e 100644 --- a/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/RootDialog.cs @@ -26,6 +26,8 @@ namespace Vsar.TSBot.Dialogs [Serializable] public class RootDialog : IDialog { + private bool initialized; + /// public Task StartAsync(IDialogContext context) { @@ -40,12 +42,15 @@ private async Task MessageReceivedAsync(IDialogContext context, IAwaitable(); // Occurs when the conversation starts. - if (string.Equals(activity.Type, ActivityTypes.ConversationUpdate, StringComparison.OrdinalIgnoreCase)) + if (!this.initialized) { + this.initialized = true; await this.Welcome(context, activity); } - - await this.ProcessCommand(context, activity, telemetryClient); + else + { + await this.ProcessCommand(context, activity, telemetryClient); + } } private async Task ProcessCommand(IDialogContext context, IMessageActivity activity, TelemetryClient telemetryClient) @@ -81,7 +86,7 @@ private async Task Welcome(IDialogContext context, IMessageActivity activity) await context.PostAsync(string.Format(Labels.WelcomeNewUser, activity.From.Name)); var dialog = GlobalConfiguration.Configuration.DependencyResolver.Find("connect"); - context.Call(dialog, this.ResumeAfterChildDialog); + await context.Forward(dialog, this.ResumeAfterChildDialog, activity, CancellationToken.None); } else { From c54566717bbe226a537741b0801159d653c63fde Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sat, 6 May 2017 23:14:45 +0200 Subject: [PATCH 4/9] Finished the flow for connecting to an account. --- .../Controllers/AuthorizeController.cs | 5 +- .../Dialogs/ConnectDialog.cs | 77 ++++++++++++++----- .../Extensions/BotDataBagExtensions.cs | 51 ++++++++++++ src/Team-Services-Bot.Api/Model/Authorize.cs | 31 ++++++++ .../Resources/Exceptions.Designer.cs | 9 +++ .../Resources/Exceptions.resx | 3 + .../Resources/Labels.Designer.cs | 2 +- .../Resources/Labels.resx | 2 +- .../Team-Services-Bot.Api.csproj | 1 + .../Views/Authorize/Index.cshtml | 7 +- 10 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 src/Team-Services-Bot.Api/Model/Authorize.cs diff --git a/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs b/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs index 5dc843b..3d88f81 100644 --- a/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs +++ b/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs @@ -57,6 +57,8 @@ public class AuthorizeController : Controller /// A view public async Task Index(string code, string error, string state) { + var pin = string.Empty; + try { var stateArray = (state ?? string.Empty).Split(';'); @@ -81,6 +83,7 @@ public async Task Index(string code, string error, string state) var result = Map(accounts, profile, token); var data = await this.botService.GetUserData(channelId, userId); + pin = data.GetPin(); var profiles = data.GetProfiles(); if (!profiles.Any(p => p.Id.Equals(result.Id))) @@ -98,7 +101,7 @@ public async Task Index(string code, string error, string state) throw new Exception(Exceptions.UnknownException, ex); } - return this.View(); + return this.View(new Authorize(pin)); } private static VstsProfile Map(IEnumerable accounts, Microsoft.VisualStudio.Services.Profile.Profile profile, OAuthToken token) diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index 397f4e2..97d2306 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -26,11 +26,18 @@ namespace Vsar.TSBot.Dialogs [Serializable] public class ConnectDialog : IDialog { - private const string CommandMatch = "(connect)? *(\\w*) *(\\w*)"; + private const string CommandMatchConnect = "connect *(\\w*) *(\\w*)"; + private const string CommandMatchPin = "(\\d{4})"; + private const int Min = 0; + private const int Max = 9999; private readonly string appId; private readonly string authorizeUrl; + private string account; + private bool isPinActivated; + private string teamProject; + /// /// Initializes a new instance of the class. /// @@ -58,53 +65,87 @@ public Task StartAsync(IDialogContext context) private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { var activity = await result; + var pin = context.UserData.GetPin(); var profile = context.UserData.GetCurrentProfile(); var profiles = context.UserData.GetProfiles(); var reply = context.MakeMessage(); + var text = (activity.Text ?? string.Empty).ToLowerInvariant(); - var match = Regex.Match(activity.Text.ToLowerInvariant(), CommandMatch); - if (!match.Success) + if (this.isPinActivated) { - return; + var isPinHandled = await this.HandlePin(context, activity, pin); + if (!isPinHandled) + { + return; + } + } + else + { + var match = Regex.Match(text, CommandMatchConnect); + if (match.Success) + { + this.account = match.Groups[2].Value; + this.teamProject = match.Groups[3].Value; + } } - var account = match.Groups[2].Value; - var teamProject = match.Groups[3].Value; - - if (!profiles.Any()) + if (!profiles.Any() || profile == null) { await this.Login(context, activity, reply); return; } - if (string.IsNullOrWhiteSpace(account)) + if (string.IsNullOrWhiteSpace(this.account)) { await this.SelectAccount(context, profiles, reply); return; } - context.UserData.SetCurrentAccount(account); - - if (string.IsNullOrWhiteSpace(teamProject)) + if (string.IsNullOrWhiteSpace(this.teamProject)) { // Select Team Project. } - context.UserData.SetCurrentTeamProject(teamProject); + context.UserData.SetCurrentAccount(this.account); + context.UserData.SetCurrentTeamProject(this.teamProject); + + reply.Text = string.Format(Labels.ConnectedTo, this.account); + await context.PostAsync(reply); - if (profile != null) + context.Done(reply); + } + + private string GeneratePin() + { + var generator = new Random(); + return generator.Next(Min, Max).ToString("0000"); + } + + private async Task HandlePin(IDialogContext context, IMessageActivity activity, string pin) + { + this.isPinActivated = false; + + var text = (activity.Text ?? string.Empty).ToLowerInvariant(); + var match = Regex.Match(text, CommandMatchPin); + + if (!match.Success || !string.Equals(pin, text, StringComparison.OrdinalIgnoreCase)) { - context.UserData.SetCurrentProfile(profile); - reply.Text = string.Format(Labels.ConnectedTo, account); + await context.PostAsync(Exceptions.InvalidPin); + context.Wait(this.MessageReceivedAsync); - await context.PostAsync(reply); + return false; } - context.Done(reply); + return true; } private async Task Login(IDialogContext context, IMessageActivity activity, IMessageActivity reply) { + // Set pin. + var pin = this.GeneratePin(); + context.UserData.SetPin(pin); + this.isPinActivated = true; + var card = new LoginCard(this.appId, this.authorizeUrl, activity.ChannelId, Labels.PleaseLogin, activity.From.Id); reply.Attachments.Add(card); diff --git a/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs b/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs index 878e954..e842784 100644 --- a/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs +++ b/src/Team-Services-Bot.Api/Extensions/BotDataBagExtensions.cs @@ -20,6 +20,7 @@ namespace Vsar.TSBot public static class BotDataBagExtensions { private const string Account = "Account"; + private const string Pin = "Pin"; private const string Profile = "Profile"; private const string Profiles = "Profiles"; private const string TeamProject = "TeamProject"; @@ -69,6 +70,36 @@ public static string GetCurrentTeamProject(this IBotDataBag dataBag) return dataBag.TryGetValue(TeamProject, out string teamProject) ? teamProject : string.Empty; } + /// + /// Gets the pin. + /// + /// The bot data. + /// A pin. + public static string GetPin(this BotData data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return data.GetProperty(Pin) ?? string.Empty; + } + + /// + /// Gets the pin. + /// + /// The dataBag. + /// A pin. + public static string GetPin(this IBotDataBag dataBag) + { + if (dataBag == null) + { + throw new ArgumentNullException(nameof(dataBag)); + } + + return dataBag.TryGetValue(Pin, out string result) ? result : string.Empty; + } + /// /// Get the current profile. /// @@ -194,6 +225,26 @@ public static void SetCurrentTeamProject(this IBotDataBag dataBag, string teamPr dataBag.SetValue(TeamProject, teamProject); } + /// + /// Sets the pin. + /// + /// The data bag. + /// The pin. + public static void SetPin(this IBotDataBag dataBag, string pin) + { + if (dataBag == null) + { + throw new ArgumentNullException(nameof(dataBag)); + } + + if (string.IsNullOrWhiteSpace(pin)) + { + throw new ArgumentNullException(nameof(pin)); + } + + dataBag.SetValue(Pin, pin); + } + /// /// Sets the profile. /// diff --git a/src/Team-Services-Bot.Api/Model/Authorize.cs b/src/Team-Services-Bot.Api/Model/Authorize.cs new file mode 100644 index 0000000..48b9469 --- /dev/null +++ b/src/Team-Services-Bot.Api/Model/Authorize.cs @@ -0,0 +1,31 @@ +// ——————————————————————————————— +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// +// Represents a model for the authorize view. +// +// ——————————————————————————————— + +namespace Vsar.TSBot +{ + /// + /// Represents the Authorize model. + /// + public class Authorize + { + /// + /// Initializes a new instance of the class. + /// + /// The pin + public Authorize(string pin) + { + this.Pin = pin; + } + + /// + /// Gets the pin. + /// + public string Pin { get; } + } +} \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Resources/Exceptions.Designer.cs b/src/Team-Services-Bot.Api/Resources/Exceptions.Designer.cs index 94a0e8a..4472f96 100644 --- a/src/Team-Services-Bot.Api/Resources/Exceptions.Designer.cs +++ b/src/Team-Services-Bot.Api/Resources/Exceptions.Designer.cs @@ -60,6 +60,15 @@ internal class Exceptions { } } + /// + /// Looks up a localized string similar to Sorry, I do not recognize the provided pin. Please try again.. + /// + internal static string InvalidPin { + get { + return ResourceManager.GetString("InvalidPin", resourceCulture); + } + } + /// /// Looks up a localized string similar to State is invalid. Expecting a ChannelId and UserId separated by a semicolon.. /// diff --git a/src/Team-Services-Bot.Api/Resources/Exceptions.resx b/src/Team-Services-Bot.Api/Resources/Exceptions.resx index c96559c..2f3658f 100644 --- a/src/Team-Services-Bot.Api/Resources/Exceptions.resx +++ b/src/Team-Services-Bot.Api/Resources/Exceptions.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Sorry, I do not recognize the provided pin. Please try again. + State is invalid. Expecting a ChannelId and UserId separated by a semicolon. diff --git a/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs b/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs index e68f0a0..a907cbc 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs +++ b/src/Team-Services-Bot.Api/Resources/Labels.Designer.cs @@ -97,7 +97,7 @@ internal class Labels { } /// - /// Looks up a localized string similar to Please login to Team Services.. + /// Looks up a localized string similar to Please login to Team Services. After login you will receive a four digit pin number, please enter the pin number.. /// internal static string PleaseLogin { get { diff --git a/src/Team-Services-Bot.Api/Resources/Labels.resx b/src/Team-Services-Bot.Api/Resources/Labels.resx index a8c8748..e696cb3 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.resx +++ b/src/Team-Services-Bot.Api/Resources/Labels.resx @@ -131,7 +131,7 @@ Please select an account to connect to. - Please login to Team Services. + Please login to Team Services. After login you will receive a four digit pin number, please enter the pin number. Queue Build diff --git a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj index 7b4036a..121d40c 100644 --- a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj +++ b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj @@ -274,6 +274,7 @@ + diff --git a/src/Team-Services-Bot.Api/Views/Authorize/Index.cshtml b/src/Team-Services-Bot.Api/Views/Authorize/Index.cshtml index ce63193..8f5cc2a 100644 --- a/src/Team-Services-Bot.Api/Views/Authorize/Index.cshtml +++ b/src/Team-Services-Bot.Api/Views/Authorize/Index.cshtml @@ -1,7 +1,4 @@ - -@{ - Layout = null; -} +@model Authorize @@ -12,7 +9,7 @@
- Test + Pin: @Model.Pin
From 5bd8f913c9b85f9f40c0e7a9cdf80db583606f12 Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sun, 7 May 2017 07:47:05 +0200 Subject: [PATCH 5/9] Fixed current unittests. --- .../CommonSteps.cs | 2 +- .../EchoSteps.cs | 1 - .../Features/Echo.feature | 12 -- .../Features/Echo.feature.cs | 105 ------------------ .../Team-Services-Bot.AcceptanceTests.csproj | 9 -- .../Dialogs/ConnectDialogTests.cs | 32 +++--- .../Dialogs/RootDialogTests.cs | 36 ------ .../Dialogs/ConnectDialog.cs | 17 ++- .../Dialogs/EchoDialog.cs | 48 -------- .../Team-Services-Bot.Api.csproj | 1 - 10 files changed, 30 insertions(+), 233 deletions(-) delete mode 100644 src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature delete mode 100644 src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature.cs delete mode 100644 src/Team-Services-Bot.Api/Dialogs/EchoDialog.cs diff --git a/src/Team-Services-Bot.AcceptanceTests/CommonSteps.cs b/src/Team-Services-Bot.AcceptanceTests/CommonSteps.cs index 457eb4c..99f5065 100644 --- a/src/Team-Services-Bot.AcceptanceTests/CommonSteps.cs +++ b/src/Team-Services-Bot.AcceptanceTests/CommonSteps.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // // -// Contains the specflow steps to perform an echo. +// Contains the common specflow steps. // // ——————————————————————————————— diff --git a/src/Team-Services-Bot.AcceptanceTests/EchoSteps.cs b/src/Team-Services-Bot.AcceptanceTests/EchoSteps.cs index 83a2bba..cb19ea9 100644 --- a/src/Team-Services-Bot.AcceptanceTests/EchoSteps.cs +++ b/src/Team-Services-Bot.AcceptanceTests/EchoSteps.cs @@ -9,7 +9,6 @@ namespace Vsar.TSBot.AcceptanceTests { - using System; using System.Linq; using FluentAssertions; using Microsoft.Bot.Connector.DirectLine; diff --git a/src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature b/src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature deleted file mode 100644 index 84ccef2..0000000 --- a/src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Echo - Feature file to test if the basic echo works. - Will be thrown away as soon as the project matures, - But captures the basic test flow. - -Background: - Given I started a conversation - -@Integration -Scenario: Echo - When I send a message 'Test' - Then I should receive a response 'You sent Test which was 4 characters' diff --git a/src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature.cs b/src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature.cs deleted file mode 100644 index bc6dfb3..0000000 --- a/src/Team-Services-Bot.AcceptanceTests/Features/Echo.feature.cs +++ /dev/null @@ -1,105 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:2.1.0.0 -// SpecFlow Generator Version:2.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -#region Designer generated code -#pragma warning disable -namespace Vsar.TSBot.AcceptanceTests.Features -{ - using TechTalk.SpecFlow; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "2.1.0.0")] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] - public partial class EchoFeature - { - - private static TechTalk.SpecFlow.ITestRunner testRunner; - - private static Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; - -#line 1 "Echo.feature" -#line hidden - - [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] - public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) - { - testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(null, 0); - TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Echo", "\tFeature file to test if the basic echo works.\r\n\tWill be thrown away as soon as t" + - "he project matures, \r\n\tBut captures the basic test flow.", ProgrammingLanguage.CSharp, ((string[])(null))); - testRunner.OnFeatureStart(featureInfo); - _testContext = testContext; - } - - [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] - public static void FeatureTearDown() - { - testRunner.OnFeatureEnd(); - testRunner = null; - } - - [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] - public virtual void TestInitialize() - { - if (((testRunner.FeatureContext != null) - && (testRunner.FeatureContext.FeatureInfo.Title != "Echo"))) - { - Vsar.TSBot.AcceptanceTests.Features.EchoFeature.FeatureSetup(null); - } - } - - [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] - public virtual void ScenarioTearDown() - { - testRunner.OnScenarioEnd(); - } - - public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) - { - testRunner.OnScenarioStart(scenarioInfo); - testRunner.ScenarioContext.Add("TestContext", _testContext); - } - - public virtual void ScenarioCleanup() - { - testRunner.CollectScenarioErrors(); - } - - public virtual void FeatureBackground() - { -#line 6 -#line 7 - testRunner.Given("I started a conversation", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line hidden - } - - [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] - [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Echo")] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Echo")] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("Integration")] - public virtual void Echo() - { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Echo", new string[] { - "Integration"}); -#line 10 -this.ScenarioSetup(scenarioInfo); -#line 6 -this.FeatureBackground(); -#line 11 - testRunner.When("I send a message \'Test\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 12 - testRunner.Then("I should receive a response \'You sent Test which was 4 characters\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line hidden - this.ScenarioCleanup(); - } - } -} -#pragma warning restore -#endregion diff --git a/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj b/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj index 6687355..e953b83 100644 --- a/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj +++ b/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj @@ -166,11 +166,6 @@ True Controller.feature
- - True - True - Echo.feature - @@ -180,10 +175,6 @@ SpecFlowSingleFileGenerator Controller.feature.cs - - SpecFlowSingleFileGenerator - Echo.feature.cs - Designer diff --git a/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs b/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs index 0c55679..b4f34c2 100644 --- a/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs +++ b/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs @@ -10,18 +10,16 @@ namespace Vsar.TSBot.UnitTests { using System; - using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web.Http; using Autofac; using Autofac.Integration.WebApi; + using Cards; using Dialogs; using Microsoft.ApplicationInsights; using Microsoft.Bot.Builder.Dialogs; - using Microsoft.Bot.Connector; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Resources; /// /// Contains Test methods for @@ -44,18 +42,22 @@ public ConnectDialogTests() [TestMethod] public async Task FirstTimeConnectionTest() { - var toBot = this.Fixture.CreateMessage(); - toBot.From.Id = Guid.NewGuid().ToString(); - toBot.Text = "connect anaccount"; + var fromId = Guid.NewGuid().ToString(); + var toBot1 = this.Fixture.CreateMessage(); + toBot1.From.Id = fromId; + toBot1.Text = "Hi"; + + var toBot2 = this.Fixture.CreateMessage(); + toBot2.From.Id = fromId; + toBot2.Text = "connect anaccount"; const string appId = "AnAppId"; const string authorizeUrl = "https://www.authorizationUrl.com"; const string scope = - "vso.agentpools_manage%20vso.build_execute%20vso.chat_manage%20vso.code_manage%20vso.code_status%20" + - "vso.connected_server%20vso.dashboards_manage%20vso.entitlements%20vso.extension.data_write%20" + - "vso.extension_manage%20vso.gallery_acquire%20vso.gallery_manage%20vso.identity%20vso.loadtest_write%20" + - "vso.notification_manage%20vso.packaging_manage%20vso.profile_write%20vso.project_manage%20" + - "vso.release_manage%20vso.security_manage%20vso.serviceendpoint_manage%20vso.test_write%20vso.work_write"; + "vso.agentpools%20vso.build_execute%20vso.chat_write%20vso.code%20vso.connected_server%20" + + "vso.dashboards%20vso.entitlements%20vso.extension%20vso.extension.data%20vso.gallery%20" + + "vso.identity%20vso.loadtest%20vso.notification%20vso.packaging%20vso.project%20" + + "vso.release_execute%20vso.serviceendpoint%20vso.taskgroups%20vso.test%20vso.work"; var builder = this.Fixture.Build(); builder.RegisterType(); @@ -69,19 +71,21 @@ public async Task FirstTimeConnectionTest() GlobalConfiguration.Configure(config => config.DependencyResolver = new AutofacWebApiDependencyResolver(container)); var root = new RootDialog(); - var toUser = await this.Fixture.GetResponse(container, root, toBot); + // First trigger the welcome message. + await this.Fixture.GetResponse(container, root, toBot1); + var toUser = await this.Fixture.GetResponse(container, root, toBot2); var attachment = toUser.Attachments.FirstOrDefault(); Assert.IsNotNull(attachment, "Expecting an attachment."); - var card = attachment.Content as SigninCard; + var card = attachment.Content as LoginCard; Assert.IsNotNull(card, "Missing signin card."); var button = card.Buttons.FirstOrDefault(); Assert.IsNotNull(button, "Button is missing"); var expected = - $"https://app.vssps.visualstudio.com/oauth2/authorize?client_id={appId}&response_type=Assertion&state={toBot.ChannelId};{toBot.From.Id}&scope={scope}&redirect_uri={authorizeUrl}/"; + $"https://app.vssps.visualstudio.com/oauth2/authorize?client_id={appId}&response_type=Assertion&state={toBot2.ChannelId};{toBot2.From.Id}&scope={scope}&redirect_uri={authorizeUrl}/"; Assert.AreEqual(expected.ToLower(), button.Value.ToString().ToLower(), "OAuth url is invalid."); } diff --git a/src/Team-Services-Bot.Api.UnitTests/Dialogs/RootDialogTests.cs b/src/Team-Services-Bot.Api.UnitTests/Dialogs/RootDialogTests.cs index 9727c7d..a58e353 100644 --- a/src/Team-Services-Bot.Api.UnitTests/Dialogs/RootDialogTests.cs +++ b/src/Team-Services-Bot.Api.UnitTests/Dialogs/RootDialogTests.cs @@ -9,15 +9,6 @@ namespace Vsar.TSBot.UnitTests { - using System; - using System.Threading.Tasks; - using System.Web.Http; - using Autofac; - using Autofac.Integration.WebApi; - using Dialogs; - using FluentAssertions; - using Microsoft.ApplicationInsights; - using Microsoft.Bot.Builder.Dialogs; using Microsoft.VisualStudio.TestTools.UnitTesting; /// @@ -33,32 +24,5 @@ public RootDialogTests() : base(new DialogFixture()) { } - - /// - /// Temporary - /// - /// Nothing. - [TestMethod] - public async Task EchoDialog() - { - var toBot = this.Fixture.CreateMessage(); - toBot.From.Id = Guid.NewGuid().ToString(); - toBot.Text = "Test"; - - var builder = this.Fixture.Build(); - builder.RegisterType(); - builder - .RegisterType() - .As>(); - - var container = builder.Build(); - GlobalConfiguration.Configure(config => config.DependencyResolver = new AutofacWebApiDependencyResolver(container)); - - var root = new RootDialog(); - - var toUser = await this.Fixture.GetResponse(container, root, toBot); - - toUser.Text.Should().Contain("4").And.Contain("Test"); - } } } \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index 97d2306..61108d9 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -71,6 +71,7 @@ private async Task MessageReceivedAsync(IDialogContext context, IAwaitable HandlePin(IDialogContext context, IMessageActivity acti var text = (activity.Text ?? string.Empty).ToLowerInvariant(); var match = Regex.Match(text, CommandMatchPin); - if (!match.Success || !string.Equals(pin, text, StringComparison.OrdinalIgnoreCase)) + if (match.Success && string.Equals(pin, text, StringComparison.OrdinalIgnoreCase)) { - await context.PostAsync(Exceptions.InvalidPin); - context.Wait(this.MessageReceivedAsync); - - return false; + return true; } - return true; + await context.PostAsync(Exceptions.InvalidPin); + context.Wait(this.MessageReceivedAsync); + + return false; } private async Task Login(IDialogContext context, IMessageActivity activity, IMessageActivity reply) diff --git a/src/Team-Services-Bot.Api/Dialogs/EchoDialog.cs b/src/Team-Services-Bot.Api/Dialogs/EchoDialog.cs deleted file mode 100644 index 6580cd7..0000000 --- a/src/Team-Services-Bot.Api/Dialogs/EchoDialog.cs +++ /dev/null @@ -1,48 +0,0 @@ -// ——————————————————————————————— -// -// Licensed under the MIT License. See License.txt in the project root for license information. -// -// -// Echo's the messages from the user. -// -// ——————————————————————————————— - -namespace Vsar.TSBot.Dialogs -{ - using System; - using System.Threading.Tasks; - using Microsoft.Bot.Builder.Dialogs; - using Microsoft.Bot.Connector; - - /// - /// Temporary - /// - [CommandMetadata("Test")] - [Serializable] - public class EchoDialog : IDialog - { - /// - public Task StartAsync(IDialogContext context) - { - context.Wait(this.MessageReceivedAsync); - - return Task.CompletedTask; - } - - private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) - { - var activity = await result as Activity; - - // calculate something for us to return - if (activity != null) - { - var length = (activity.Text ?? string.Empty).Length; - - // return our reply to the user - await context.PostAsync($"You sent {activity.Text} which was {length} characters"); - } - - context.Wait(this.MessageReceivedAsync); - } - } -} \ No newline at end of file diff --git a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj index 121d40c..7bfcd26 100644 --- a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj +++ b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj @@ -268,7 +268,6 @@ - From 7d4135906f469bff85c9100e6d5971e9cf010b5b Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sun, 7 May 2017 07:51:42 +0200 Subject: [PATCH 6/9] Small cleanup in the Authorize controller. --- .../Controllers/AuthorizeController.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs b/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs index 3d88f81..b2bbb0a 100644 --- a/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs +++ b/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs @@ -57,8 +57,6 @@ public class AuthorizeController : Controller /// A view public async Task Index(string code, string error, string state) { - var pin = string.Empty; - try { var stateArray = (state ?? string.Empty).Split(';'); @@ -83,7 +81,7 @@ public async Task Index(string code, string error, string state) var result = Map(accounts, profile, token); var data = await this.botService.GetUserData(channelId, userId); - pin = data.GetPin(); + var pin = data.GetPin(); var profiles = data.GetProfiles(); if (!profiles.Any(p => p.Id.Equals(result.Id))) @@ -94,14 +92,14 @@ public async Task Index(string code, string error, string state) data.SetCurrentProfile(result); data.SetProfiles(profiles); await this.botService.SetUserData(channelId, userId, data); + + return this.View(new Authorize(pin)); } catch (Exception ex) { this.telemetryClient.TrackException(ex); throw new Exception(Exceptions.UnknownException, ex); } - - return this.View(new Authorize(pin)); } private static VstsProfile Map(IEnumerable accounts, Microsoft.VisualStudio.Services.Profile.Profile profile, OAuthToken token) From 47fa8760b4bf5de80d9a8ecc29bd7e3e1e641cef Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sun, 7 May 2017 21:16:51 +0200 Subject: [PATCH 7/9] Some minor fixes. --- src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs | 7 ++++--- src/Team-Services-Bot.Api/Resources/Labels.Designer.cs | 2 +- src/Team-Services-Bot.Api/Resources/Labels.resx | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index 61108d9..f967f67 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -86,8 +86,8 @@ private async Task MessageReceivedAsync(IDialogContext context, IAwaitable - /// Looks up a localized string similar to Connected to {0}.. + /// Looks up a localized string similar to Connected to {0} / {1}.. /// internal static string ConnectedTo { get { diff --git a/src/Team-Services-Bot.Api/Resources/Labels.resx b/src/Team-Services-Bot.Api/Resources/Labels.resx index e696cb3..5f340cb 100644 --- a/src/Team-Services-Bot.Api/Resources/Labels.resx +++ b/src/Team-Services-Bot.Api/Resources/Labels.resx @@ -124,8 +124,8 @@ Authentication is required. - Connected to {0}. - {0} = accountname; + Connected to {0} / {1}. + {0} = accountname; {1} = team project. Please select an account to connect to. From e1f08d4750ddcd0bbd3bcbd78bc654a18932ef44 Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sun, 7 May 2017 21:34:11 +0200 Subject: [PATCH 8/9] - Updated Nuget packages. - Followed up on new rulesets. --- src/Common/ProductionCodeRules.ruleset | 3 ++- .../Team-Services-Bot.AcceptanceTests.csproj | 22 +++++++++---------- .../packages.config | 8 +++---- .../Dialogs/ConnectDialogTests.cs | 2 +- .../Team-Services-Bot.Api.UnitTests.csproj | 18 +++++++-------- .../packages.config | 6 ++--- .../Cards/{LoginCard.cs => LogOnCard.cs} | 9 ++++---- .../Dialogs/ConnectDialog.cs | 17 +++++++------- .../Team-Services-Bot.Api.csproj | 16 +++++++------- src/Team-Services-Bot.Api/packages.config | 4 ++-- 10 files changed, 54 insertions(+), 51 deletions(-) rename src/Team-Services-Bot.Api/Cards/{LoginCard.cs => LogOnCard.cs} (88%) diff --git a/src/Common/ProductionCodeRules.ruleset b/src/Common/ProductionCodeRules.ruleset index 807cd27..b2ecb35 100644 --- a/src/Common/ProductionCodeRules.ruleset +++ b/src/Common/ProductionCodeRules.ruleset @@ -1,8 +1,9 @@  - + + diff --git a/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj b/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj index e953b83..b964c4b 100644 --- a/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj +++ b/src/Team-Services-Bot.AcceptanceTests/Team-Services-Bot.AcceptanceTests.csproj @@ -75,23 +75,23 @@ ..\packages\SpecFlow.CustomPlugin.2.1.0\lib\net45\Gherkin.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Builder.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Connector.dll - - ..\packages\Microsoft.Bot.Connector.DirectLine.3.0.0\lib\net45\Microsoft.Bot.Connector.DirectLine.dll + + ..\packages\Microsoft.Bot.Connector.DirectLine.3.0.1\lib\net45\Microsoft.Bot.Connector.DirectLine.dll ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.4.403061554\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll - ..\packages\Microsoft.Rest.ClientRuntime.2.3.6\lib\net45\Microsoft.Rest.ClientRuntime.dll + ..\packages\Microsoft.Rest.ClientRuntime.2.3.7\lib\net452\Microsoft.Rest.ClientRuntime.dll ..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -100,8 +100,8 @@ ..\packages\MSTest.TestFramework.1.1.17\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll False - - ..\packages\Moq.4.7.9\lib\net45\Moq.dll + + ..\packages\Moq.4.7.10\lib\net45\Moq.dll ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll diff --git a/src/Team-Services-Bot.AcceptanceTests/packages.config b/src/Team-Services-Bot.AcceptanceTests/packages.config index cbf9020..5aee4c1 100644 --- a/src/Team-Services-Bot.AcceptanceTests/packages.config +++ b/src/Team-Services-Bot.AcceptanceTests/packages.config @@ -18,12 +18,12 @@ - - + + - - + + diff --git a/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs b/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs index b4f34c2..2f1358c 100644 --- a/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs +++ b/src/Team-Services-Bot.Api.UnitTests/Dialogs/ConnectDialogTests.cs @@ -78,7 +78,7 @@ public async Task FirstTimeConnectionTest() var attachment = toUser.Attachments.FirstOrDefault(); Assert.IsNotNull(attachment, "Expecting an attachment."); - var card = attachment.Content as LoginCard; + var card = attachment.Content as LogOnCard; Assert.IsNotNull(card, "Missing signin card."); var button = card.Buttons.FirstOrDefault(); diff --git a/src/Team-Services-Bot.Api.UnitTests/Team-Services-Bot.Api.UnitTests.csproj b/src/Team-Services-Bot.Api.UnitTests/Team-Services-Bot.Api.UnitTests.csproj index 0f86c30..1655b06 100644 --- a/src/Team-Services-Bot.Api.UnitTests/Team-Services-Bot.Api.UnitTests.csproj +++ b/src/Team-Services-Bot.Api.UnitTests/Team-Services-Bot.Api.UnitTests.csproj @@ -80,20 +80,20 @@ ..\packages\Microsoft.ApplicationInsights.2.3.0\lib\net46\Microsoft.ApplicationInsights.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Builder.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Connector.dll ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.4.403061554\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll - ..\packages\Microsoft.Rest.ClientRuntime.2.3.6\lib\net45\Microsoft.Rest.ClientRuntime.dll + ..\packages\Microsoft.Rest.ClientRuntime.2.3.7\lib\net452\Microsoft.Rest.ClientRuntime.dll ..\packages\Microsoft.VisualStudio.Services.Client.15.112.1\lib\net45\Microsoft.TeamFoundation.Common.dll @@ -113,8 +113,8 @@ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\packages\Moq.4.7.9\lib\net45\Moq.dll + + ..\packages\Moq.4.7.10\lib\net45\Moq.dll ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll diff --git a/src/Team-Services-Bot.Api.UnitTests/packages.config b/src/Team-Services-Bot.Api.UnitTests/packages.config index fd99cce..dd37f73 100644 --- a/src/Team-Services-Bot.Api.UnitTests/packages.config +++ b/src/Team-Services-Bot.Api.UnitTests/packages.config @@ -26,13 +26,13 @@ - + - + - + diff --git a/src/Team-Services-Bot.Api/Cards/LoginCard.cs b/src/Team-Services-Bot.Api/Cards/LogOnCard.cs similarity index 88% rename from src/Team-Services-Bot.Api/Cards/LoginCard.cs rename to src/Team-Services-Bot.Api/Cards/LogOnCard.cs index 6138d40..925c4a6 100644 --- a/src/Team-Services-Bot.Api/Cards/LoginCard.cs +++ b/src/Team-Services-Bot.Api/Cards/LogOnCard.cs @@ -1,5 +1,5 @@ // ——————————————————————————————— -// +// // Licensed under the MIT License. See License.txt in the project root for license information. // // @@ -9,6 +9,7 @@ namespace Vsar.TSBot.Cards { + using System; using System.Collections.Generic; using Microsoft.Bot.Connector; using Resources; @@ -16,7 +17,7 @@ namespace Vsar.TSBot.Cards /// /// Represent the login card. /// - public class LoginCard : SigninCard + public class LogOnCard : SigninCard { private const string Scope = "vso.agentpools%20vso.build_execute%20vso.chat_write%20vso.code%20vso.connected_server%20" + "vso.dashboards%20vso.entitlements%20vso.extension%20vso.extension.data%20vso.gallery%20" + @@ -26,14 +27,14 @@ public class LoginCard : SigninCard private const string UrlOAuth = "https://app.vssps.visualstudio.com/oauth2/authorize?client_id={0}&response_type=Assertion&state={1};{2}&scope={3}&redirect_uri={4}"; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The app id. /// The authorizeUrl. /// The channelId. /// The text on the card. /// The userId. - public LoginCard(string appId, string authorizeUrl, string channelId, string text, string userId) + public LogOnCard(string appId, Uri authorizeUrl, string channelId, string text, string userId) : base(text) { var button = new CardAction diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index f967f67..c849b0c 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -11,6 +11,7 @@ namespace Vsar.TSBot.Dialogs { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -62,6 +63,12 @@ public Task StartAsync(IDialogContext context) return Task.CompletedTask; } + private static string GeneratePin() + { + var generator = new Random(); + return generator.Next(Min, Max).ToString("0000", CultureInfo.InvariantCulture); + } + private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { var activity = await result; @@ -121,12 +128,6 @@ private async Task MessageReceivedAsync(IDialogContext context, IAwaitable HandlePin(IDialogContext context, IMessageActivity activity, string pin) { this.isPinActivated = false; @@ -148,11 +149,11 @@ private async Task HandlePin(IDialogContext context, IMessageActivity acti private async Task Login(IDialogContext context, IMessageActivity activity, IMessageActivity reply) { // Set pin. - var pin = this.GeneratePin(); + var pin = GeneratePin(); context.UserData.SetPin(pin); this.isPinActivated = true; - var card = new LoginCard(this.appId, this.authorizeUrl, activity.ChannelId, Labels.PleaseLogin, activity.From.Id); + var card = new LogOnCard(this.appId, new Uri(this.authorizeUrl), activity.ChannelId, Labels.PleaseLogin, activity.From.Id); reply.Attachments.Add(card); diff --git a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj index 7bfcd26..0b6d7b4 100644 --- a/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj +++ b/src/Team-Services-Bot.Api/Team-Services-Bot.Api.csproj @@ -97,14 +97,14 @@ ..\packages\Microsoft.ApplicationInsights.2.3.0\lib\net46\Microsoft.ApplicationInsights.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Builder.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Builder.Autofac.dll - - ..\packages\Microsoft.Bot.Builder.3.5.8.0\lib\net46\Microsoft.Bot.Connector.dll + + ..\packages\Microsoft.Bot.Builder.3.5.9.0\lib\net46\Microsoft.Bot.Connector.dll @@ -117,7 +117,7 @@ ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.4.403061554\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll - ..\packages\Microsoft.Rest.ClientRuntime.2.3.6\lib\net45\Microsoft.Rest.ClientRuntime.dll + ..\packages\Microsoft.Rest.ClientRuntime.2.3.7\lib\net452\Microsoft.Rest.ClientRuntime.dll ..\packages\WindowsAzure.ServiceBus.4.0.0\lib\net45-full\Microsoft.ServiceBus.dll @@ -262,7 +262,7 @@ - + diff --git a/src/Team-Services-Bot.Api/packages.config b/src/Team-Services-Bot.Api/packages.config index 08e17ff..8ec7c9a 100644 --- a/src/Team-Services-Bot.Api/packages.config +++ b/src/Team-Services-Bot.Api/packages.config @@ -35,11 +35,11 @@ - + - + From e5ec4eca00e697b56fa3acba905f4b69f6fd2ccd Mon Sep 17 00:00:00 2001 From: Jeffrey Opdam Date: Sun, 7 May 2017 21:39:40 +0200 Subject: [PATCH 9/9] Added guards in ConnectDialog Removed try..catch --- .../Controllers/AuthorizeController.cs | 64 ++++++++----------- .../Dialogs/ConnectDialog.cs | 10 +++ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs b/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs index b2bbb0a..2669035 100644 --- a/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs +++ b/src/Team-Services-Bot.Api/Controllers/AuthorizeController.cs @@ -26,7 +26,6 @@ public class AuthorizeController : Controller private readonly IAuthenticationService authenticationService; private readonly IBotService botService; private readonly IProfileService profileService; - private readonly TelemetryClient telemetryClient; /// /// Initializes a new instance of the class. @@ -44,7 +43,6 @@ public class AuthorizeController : Controller this.authenticationService = authenticationService; this.botService = botService; this.profileService = profileService; - this.telemetryClient = telemetryClient; } /// @@ -57,49 +55,41 @@ public class AuthorizeController : Controller /// A view public async Task Index(string code, string error, string state) { - try - { - var stateArray = (state ?? string.Empty).Split(';'); - - if (string.IsNullOrWhiteSpace(code) && string.IsNullOrWhiteSpace(error)) - { - throw new ArgumentNullException(nameof(code)); - } - - if (stateArray.Length != 2) - { - throw new ArgumentException(Exceptions.InvalidState, nameof(state)); - } + var stateArray = (state ?? string.Empty).Split(';'); - var channelId = stateArray[0]; - var userId = stateArray[1]; + if (string.IsNullOrWhiteSpace(code) && string.IsNullOrWhiteSpace(error)) + { + throw new ArgumentNullException(nameof(code)); + } - // Get the security token. - var token = await this.authenticationService.GetToken(code); - var profile = await this.profileService.GetProfile(token); - var accounts = await this.profileService.GetAccounts(token, profile.Id); - var result = Map(accounts, profile, token); + if (stateArray.Length != 2) + { + throw new ArgumentException(Exceptions.InvalidState, nameof(state)); + } - var data = await this.botService.GetUserData(channelId, userId); - var pin = data.GetPin(); - var profiles = data.GetProfiles(); + var channelId = stateArray[0]; + var userId = stateArray[1]; - if (!profiles.Any(p => p.Id.Equals(result.Id))) - { - profiles.Add(result); - } + // Get the security token. + var token = await this.authenticationService.GetToken(code); + var profile = await this.profileService.GetProfile(token); + var accounts = await this.profileService.GetAccounts(token, profile.Id); + var result = Map(accounts, profile, token); - data.SetCurrentProfile(result); - data.SetProfiles(profiles); - await this.botService.SetUserData(channelId, userId, data); + var data = await this.botService.GetUserData(channelId, userId); + var pin = data.GetPin(); + var profiles = data.GetProfiles(); - return this.View(new Authorize(pin)); - } - catch (Exception ex) + if (!profiles.Any(p => p.Id.Equals(result.Id))) { - this.telemetryClient.TrackException(ex); - throw new Exception(Exceptions.UnknownException, ex); + profiles.Add(result); } + + data.SetCurrentProfile(result); + data.SetProfiles(profiles); + await this.botService.SetUserData(channelId, userId, data); + + return this.View(new Authorize(pin)); } private static VstsProfile Map(IEnumerable accounts, Microsoft.VisualStudio.Services.Profile.Profile profile, OAuthToken token) diff --git a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs index c849b0c..0d1fe35 100644 --- a/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs +++ b/src/Team-Services-Bot.Api/Dialogs/ConnectDialog.cs @@ -71,6 +71,16 @@ private static string GeneratePin() private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + var activity = await result; var pin = context.UserData.GetPin(); var profile = context.UserData.GetCurrentProfile();