Skip to content

Instantly share code, notes, and snippets.

@jsiegmund
Created August 11, 2020 06:30
Show Gist options
  • Save jsiegmund/831d5337b1a438133991070daba8a27e to your computer and use it in GitHub Desktop.
Save jsiegmund/831d5337b1a438133991070daba8a27e to your computer and use it in GitHub Desktop.
public class HourBookingDialog : AuthenticatedDialog
{
private readonly string _propNameModel = "HourBookingModel";
private readonly string _propNameCustomers = "Customers";
private readonly string _propNameProjects = "Projects";
public HourBookingDialog(IConfiguration configuration) : base (nameof(HourBookingDialog), configuration["ConnectionName"])
{
var waterFallSteps = new WaterfallStep[]
{
PromptStep,
LoginStep,
CustomerStep,
ProjectStep,
HourTypeStep,
AmountStep,
DateStep,
ConfirmStep,
BookingStep
};
AddDialog(new OAuthPrompt(
nameof(OAuthPrompt),
new OAuthPromptSettings
{
ConnectionName = ConnectionName,
Text = "Log into Exact Online. Please paste back the verification number in the chat.",
Title = "Login",
Timeout = 300000, // User has 5 minutes to login
}));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterFallSteps));
AddDialog(new GuidPrompt(nameof(CustomerStep)));
AddDialog(new GuidPrompt(nameof(ProjectStep)));
AddDialog(new GuidPrompt(nameof(HourTypeStep)));
AddDialog(new NumberPrompt<long>(nameof(AmountStep)));
AddDialog(new DateTimePrompt(nameof(DateStep)));
AddDialog(new ConfirmPrompt(nameof(ConfirmStep)));
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> PromptStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var botAdapter = (BotFrameworkAdapter)stepContext.Context.Adapter;
await botAdapter.SignOutUserAsync(stepContext.Context, ConnectionName, null, cancellationToken);
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}
private async Task<DialogTurnResult> LoginStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Get the token from the previous step. Note that we could also have gotten the
// token directly from the prompt itself. There is an example of this in the next method.
var tokenResponse = (TokenResponse)stepContext.Result;
if (tokenResponse != null)
{
stepContext.Values["AccessToken"] = tokenResponse;
await stepContext.Context.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken);
return await stepContext.ContinueDialogAsync();
}
else
{
return await stepContext.ReplaceDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}
}
private AdaptiveCard RegisterCustomerDialog(IEnumerable<HourBookingCustomerModel> customers)
{
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2))
{
Body = new List<AdaptiveElement>
{
new AdaptiveTextBlock("Please select a customer"),
}
};
card.Body.Add(new AdaptiveActionSet()
{
Actions = customers.Select(c => new AdaptiveSubmitAction() { Data = c.Id, Title = c.Name }).ToList<AdaptiveAction>()
});
AddDialog(new AdaptiveCardPrompt(nameof(CustomerStep), new AdaptiveCardPromptSettings()
{
Card = new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
},
PromptId = "customerPrompt"
}));
return card;
}
private async Task<DialogTurnResult> CustomerStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var timeRegistrationRepository = new TimeRegistrationRepository(() => GetExactAccessToken(stepContext.Context));
var accounts = timeRegistrationRepository.GetRecentAccounts();
var customers = accounts.Select(a => new HourBookingCustomerModel() { Id = a.AccountId, Name = a.AccountName });
if (customers.Count() == 0)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("I checked Exact but didn't find any recent hours booked. At the moment I'm limited to booking hours on customers you've previously used."));
return await stepContext.EndDialogAsync();
}
else if (customers.Count() == 1)
{
var customerId = customers.First().Id;
stepContext.Values[nameof(HourBookingModel)] = new HourBookingModel() { CustomerId = customerId };
return await stepContext.ContinueDialogAsync();
}
else
{
var card = RegisterCustomerDialog(customers);
return await stepContext.PromptAsync(
nameof(CustomerStep),
new PromptOptions()
{
Prompt = (Activity)MessageFactory.Attachment(new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
})
},
cancellationToken
);
}
}
private AdaptiveCard RegisterProjectDialog(ITurnContext turnContext, Guid customerId)
{
var timeRegistrationRepository = new TimeRegistrationRepository(() => GetExactAccessToken(turnContext));
var projects = timeRegistrationRepository.GetRecentProjectsByAccountId(customerId);
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2))
{
Body = new List<AdaptiveElement>
{
new AdaptiveTextBlock("Please select a project"),
}
};
card.Body.Add(new AdaptiveActionSet()
{
Actions = projects.Select(c => new AdaptiveSubmitAction() { Data = c.ProjectId, Title = c.ProjectDescription }).ToList<AdaptiveAction>()
});
AddDialog(new AdaptiveCardPrompt(nameof(ProjectStep), new AdaptiveCardPromptSettings()
{
Card = new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
},
PromptId = "projectPrompt"
}
));
return card;
}
private async Task<DialogTurnResult> ProjectStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Save the value of the previous step (Customer Selection)
if (stepContext.Result is Guid)
{
var customerId = (Guid)stepContext.Result;
stepContext.Values[nameof(HourBookingModel)] = new HourBookingModel() { CustomerId = customerId };
}
var card = RegisterProjectDialog(stepContext.Context, customerId);
return await stepContext.PromptAsync(
nameof(ProjectStep),
new PromptOptions()
{
Prompt = (Activity)MessageFactory.Attachment(new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
})
},
cancellationToken
);
}
private AdaptiveCard RegisterHourTypeDialog(ITurnContext turnContext)
{
var timeRegistrationRepository = new TimeRegistrationRepository(() => GetExactAccessToken(turnContext));
var projects = timeRegistrationRepository.GetRecentHourCostTypes();
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2))
{
Body = new List<AdaptiveElement>
{
new AdaptiveTextBlock("Please select a cost (hour) type."),
}
};
card.Body.Add(new AdaptiveActionSet()
{
Actions = projects.Select(c => new AdaptiveSubmitAction() { Data = c.ItemId, Title = c.ItemDescription}).ToList<AdaptiveAction>()
});
AddDialog(new AdaptiveCardPrompt(nameof(HourTypeStep), new AdaptiveCardPromptSettings()
{
Card = new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
},
PromptId = "hourPrompt"
}));
return card;
}
private async Task<DialogTurnResult> HourTypeStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// save the value of the previous step (Project Selection)
if (stepContext.Result is Guid)
{
var model = (HourBookingModel)stepContext.Values[nameof(HourBookingModel)];
var projectId = (Guid)stepContext.Result;
model.ProjectId = projectId;
}
var card = RegisterHourTypeDialog(stepContext.Context);
return await stepContext.PromptAsync(
nameof(ProjectStep),
new PromptOptions()
{
Prompt = (Activity)MessageFactory.Attachment(new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
})
},
cancellationToken
);
}
private async Task<DialogTurnResult> AmountStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (stepContext.Result is Guid)
{
var model = (HourBookingModel)stepContext.Values[nameof(HourBookingModel)];
var hourTypeId = (Guid)stepContext.Result;
model.HourTypeId = hourTypeId;
}
return await stepContext.PromptAsync(nameof(AmountStep), new PromptOptions { Prompt = MessageFactory.Text($"Enter the amount of hours") }, cancellationToken);
}
private async Task<DialogTurnResult> DateStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var model = (HourBookingModel)stepContext.Values[nameof(HourBookingModel)];
var amount = (long)stepContext.Result;
model.NumberOfHours = amount;
return await stepContext.PromptAsync(nameof(DateStep), new PromptOptions { Prompt = MessageFactory.Text($"Enter the date on which to book") }, cancellationToken);
}
private async Task<DialogTurnResult> ConfirmStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var model = (HourBookingModel)stepContext.Values[nameof(HourBookingModel)];
var dateResolution = (IList<DateTimeResolution>)stepContext.Result;
model.Date = DateTime.Parse(dateResolution.FirstOrDefault().Value);
return await stepContext.PromptAsync(nameof(ConfirmStep), new PromptOptions { Prompt = MessageFactory.Text($"Please confirm that you want to book {model.NumberOfHours} hours.") }, cancellationToken);
}
private async Task<DialogTurnResult> BookingStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var confirmed = (bool)stepContext.Result;
if (confirmed)
{
var hourBookingModel = stepContext.GetValue<HourBookingModel>(nameof(HourBookingModel));
var employeeRepository = new EmployeeRepository(() => GetExactAccessToken(stepContext.Context));
var timeRegistrationRepository = new TimeRegistrationRepository(() => GetExactAccessToken(stepContext.Context));
var employeeId = employeeRepository.GetEmployeeID();
var date = hourBookingModel.Date.HasValue ? hourBookingModel.Date.Value : DateTime.Now;
var amount = hourBookingModel.NumberOfHours.HasValue ? hourBookingModel.NumberOfHours.Value : 8;
// do the actual booking stuff here
timeRegistrationRepository.BookHours(employeeId, hourBookingModel.CustomerId, hourBookingModel.HourTypeId, hourBookingModel.ProjectId, date, amount);
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Great, I've booked your hours into Exact Online!"));
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("That's ok! Feel free to ask me again to book your hours."));
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
private string GetExactAccessToken(ITurnContext turnContext)
{
var botAdapter = (BotFrameworkAdapter)turnContext.Adapter;
var getTokenTask = botAdapter.GetUserTokenAsync(turnContext, ConnectionName, null);
getTokenTask.Wait();
return getTokenTask.Result?.Token;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment