To this point, we’ve been creating the building blocks that will enable our Help Desk dashboard. Let’s take all of that effort and put it to good use! There are some changes to the various _Layout.cshtml files and such, but the site is driven completely by Home/Index.cshtml, so let’s start there.
The home screen contains four quadrants. In the upper left, the incoming e-mails. In the upper right, the Open Support Tickets for the current user. In the bottom left, the Operations Yammer group feed. In the bottom right, the SharePoint Announcements. We’re just going to create four divs to contains each of components. The Index.cshtml file looks like this:
@{
ViewBag.Title = "Home Page";
}
<div class="dashboard-tile float-left">
<h3>
Incoming E-mail Messages
</h3>
<div class="dashboard-tile-content" id="email-content">
</div>
</div>
<div class="dashboard-tile float-right">
<h3>
My Tickets
</h3>
<div class="dashboard-tile-content" id="support-ticket-content">
</div>
</div>
<div class="dashboard-tile float-left">
<h3>
Operations Yammer Feed
</h3>
<div class="dashboard-tile-content">
<div id="embedded-feed"></div>
</div>
</div>
<div class="dashboard-tile float-right">
<h3>
Operations Announcements
</h3>
<div class="dashboard-tile-content" id="announcement-content">
</div>
</div>
@section scripts
{
<script src="~/Scripts/Email.js"></script>
<script src="~/Scripts/SupportTicket.js"></script>
<script src="~/Scripts/Home.js"></script>
<!-- Embed Yammer -->
<script type="text/javascript" src="https://assets.yammer.com/assets/platform_embed.js"></script>
<script type="text/javascript">
yam.connect.embedFeed({
container: "#embedded-feed",
network: "jonhussdev.com",
feedType: "group",
feedId: "6791432"});
</script>
}
At the bottom you’ll note a section that embeds Yammer. Arguably, embedding a Yammer group feed is one of the simplest aspects to this application. It’s just a matter of finding the Yammer group and choosing ‘Embed this feed in your site’:
In the window that opens, just copy the code.
Then just paste it into the web page and move the ‘embedded-feed’ div to wherever it should be displayed. Simple, right?
Next, we need to create the Controllers that will drive the screen. There are three controllers, one for each quadrant (discounting the Yammer quadrant, since it’s already done).
Controllers
The first controller is the EmailController. It has three jobs: retrieve the incoming e-mails, get details for an individual e-mail, and remove discarded e-mails. For all three of these tasks, the EmailController will utilize the GraphHelper.cs file that we created earlier. The EmailController looks like this:
using BusinessApps.HelpDesk.Helpers;
using BusinessApps.HelpDesk.Models.Email;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
namespace BusinessApps.HelpDesk.Controllers
{
public class EmailController : Controller
{
// GET: Email
[HttpGet]
public async Task<JsonResult> Index()
{
GraphHelper graphHelper = new GraphHelper();
IEnumerable<EmailMessage> messages = await graphHelper.GetNewEmailMessages();
return Json(messages.OrderByDescending(m => m.SentTimestamp), JsonRequestBehavior.AllowGet);
}
public async Task<PartialViewResult> EmailDetails(string messageId)
{
GraphHelper graphHelper = new GraphHelper();
EmailMessage message = await graphHelper.GetEmailMessage(messageId);
EmailDetailsViewModel model = new EmailDetailsViewModel();
model.EmailMessage = message;
model.HelpdeskOperators = await graphHelper.GetHelpdeskOperators();
return PartialView("_EmailDetails", model);
}
public async Task DiscardEmail(string messageId)
{
GraphHelper graphHelper = new GraphHelper();
await graphHelper.DeleteEmailMessage(messageId);
}
}
}
The next controller is the AnnouncementController. It has one job: retrieve the announcements from the SharePoint Announcements app. The AnnouncementController utilizes the SharePointHelper.cs class that we created earlier and looks like this:
using BusinessApps.HelpDesk.Helpers;
using BusinessApps.HelpDesk.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
namespace BusinessApps.HelpDesk.Controllers
{
public class AnnouncementController : Controller
{
// GET: Announcement
public async Task<JsonResult> Index()
{
SharePointHelper sharepointHelper = new SharePointHelper();
IEnumerable<Announcement> announcements = await sharepointHelper.GetAnnouncements();
return Json(announcements.OrderByDescending(a => a.Timestamp), JsonRequestBehavior.AllowGet);
}
}
}
The last controller is the SupportTicketController. This controller is certainly the most complex of the bunch. It has four jobs: get the open Support Tickets for the current user, get the details for an individual Support Ticket, assign a Support Ticket to an Operator, and finally, Close a Support Ticket. It uses Entity Framework to access the database and perform the CRUD operations. It also uses the GraphHelper.cs class to gather the group of Help Desk Operators and to change the Category of the e-mail, which indicates if the e-mail has been ‘Assigned’ or ‘Closed’. The SupportTicketController looks like this:
using BusinessApps.HelpDesk.Data;
using BusinessApps.HelpDesk.Helpers;
using BusinessApps.HelpDesk.Models.SupportTicket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
namespace BusinessApps.HelpDesk.Controllers
{
public class SupportTicketController : Controller
{
// GET: SupportTicket
public JsonResult GetSupportTicketsForUser()
{
HelpDeskDatabase db = new HelpDeskDatabase();
IEnumerable<SupportTicket> tickets = db.SupportTickets.Where(s => s.AssignedTo.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase) && s.Status == "Open");
return Json(tickets, JsonRequestBehavior.AllowGet);
}
public PartialViewResult GetSupportTicketDetails(string supportTicketID)
{
HelpDeskDatabase db = new HelpDeskDatabase();
SupportTicketViewModel model = new SupportTicketViewModel();
model.SupportTicket = db.SupportTickets.Find(Int32.Parse(supportTicketID));
return PartialView("_SupportTicketDetails", model);
}
[HttpPost]
public async Task AssignSupportTicket(string messageID, string title, string description, string assignedTo)
{
SupportTicket supportTicket = new SupportTicket();
supportTicket.AssignedTo = assignedTo;
supportTicket.MessageID = messageID;
supportTicket.Title = Uri.UnescapeDataString(title);
supportTicket.Description = Uri.UnescapeDataString(description);
supportTicket.Status = "Open";
HelpDeskDatabase db = new HelpDeskDatabase();
db.SupportTickets.Add(supportTicket);
db.SaveChanges();
GraphHelper graphHelper = new GraphHelper();
await graphHelper.MarkEmailMessageAssigned(messageID);
}
[HttpPost]
public async Task CloseSupportTicket(string supportTicketID)
{
HelpDeskDatabase db = new HelpDeskDatabase();
SupportTicket supportTicket = db.SupportTickets.Find(Int32.Parse(supportTicketID));
supportTicket.Status = "Closed";
db.SaveChanges();
GraphHelper graphHelper = new GraphHelper();
await graphHelper.MarkEmailMessageClosed(supportTicket.MessageID);
}
}
}
JavaScript
Because this is a single page application, all of the necessary operations are done via JavaScript and AJAX. There are three JavaScript files: Home.js, Email.js, and SupportTicket.js. Home.js drives the Home screen. It makes three AJAX calls into the three controllers discussed above to load the data on the home screen. It also sets timers so that each of the three quadrants are re-loaded every 5 seconds. Home.js looks like this:
function GetEmails() {
$.ajax({
type: "GET",
url: "/Email",
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#email-content").html('');
for (var i = 0; i < data.length; i++) {
emailHtml = "<div class='email-message' onclick='DisplayEmail(\"" + data[i].MessageID + "\")'>" +
"<span class='email-text'>" + data[i].Subject.substr(0, 40) + "</span>" +
"<span class='email-timestamp'>" + new Date(data[i].SentTimestamp + " UTC").toLocaleString() + "</span>" +
"</div>";
$("#email-content").append(emailHtml);
}
}
});
}
function GetTickets(userEmailAddress) {
$.ajax({
type: "GET",
url: "/SupportTicket/GetSupportTicketsForUser?userEmailAddress=" + userEmailAddress,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#support-ticket-content").html('');
for (var i = 0; i < data.length; i++) {
ticketHtml = "<div class='support-ticket-message' onclick='DisplaySupportTicket(\"" + data[i].ID + "\")'>" +
"<span class='support-ticket-title'>" + data[i].Title.substr(0, 40) + "</span>" +
"<span class='support-ticket-status'>" + data[i].Status + "</span>" +
"</div>";
$("#support-ticket-content").append(ticketHtml);
}
}
});
}
function GetAnnouncements() {
$.ajax({
type: "GET",
url: "/Announcement",
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#announcement-content").html('');
for (var i = 0; i < data.length; i++) {
var date = new Date(parseInt(data[i].Timestamp.replace("/Date(", "").replace(")/", ""), 10));
html = "<div class='announcement'>" +
"<span class='announcement-title'>" + data[i].Title + "</span>" +
" - " +
"<span class='accouncement-timestamp'>" + date.toLocaleString() + "</span>" +
"</div>";
$("#announcement-content").append(html);
}
}
});
}
window.onload = function () {
GetEmails();
GetTickets();
GetAnnouncements();
setInterval('GetEmails()', 5000);
setInterval('GetTickets()', 5000);
setInterval('GetAnnouncements()', 5000);
}
Email.js drives the Email Details screen. It has three functions: Display the e-mail details, assign an e-mail to an Operator (as a Support Ticket), and discard an e-mail that shouldn’t have been sent to the Operations Help Desk. All three of these functions are also AJAX calls that are directed at the EmailController. Email.js looks like this:
function DisplayEmail(messageId) {
$.ajax({
type: "GET",
url: "/Email/EmailDetails?messageId=" + messageId,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#dialog").html(data);
$("#dialog").dialog({
width: 700,
height: 500,
title: "Message Details"
});
}
});
}
function Assign(messageId, subject, body) {
var title = escape($("#email-message-subject").text());
var description = escape($("#email-message-body").text());
var assignedTo = $(".user-dropdown li.selected").attr("data-user");
$.ajax({
type: "POST",
url: "/SupportTicket/AssignSupportTicket?messageID=" + messageId,
data: {
"description": description,
"title": title,
"assignedTo": assignedTo
},
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#dialog").dialog("close");
}
});
}
function Discard(messageId) {
$.ajax({
type: "POST",
url: "/Email/DiscardEmail?messageId=" + messageId,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#dialog").dialog("close");
}
});
}
Finally, SupportTicket.js has two functions: Display the details of a Support Ticket and close a Support Ticket. Again, two AJAX functions that call directly into the SupportTicketController. SupportTicket.js looks like this:
function DisplaySupportTicket(supportTicketID) {
$.ajax({
type: "GET",
url: "/SupportTicket/GetSupportTicketDetails?supportTicketID=" + supportTicketID,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#dialog").html(data);
$("#dialog").dialog({
width: 700,
height: 500,
title: "Support Ticket Details"
});
}
});
}
function CloseTicket(supportTicketID) {
$.ajax({
type: "POST",
url: "/SupportTicket/CloseSupportTicket?supportTicketID=" + supportTicketID,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data) {
$("#dialog").dialog("close");
}
});
}
The Finished Product
If we run the application, it looks like this:
Not bad, right? Clicking on the “Help! My e-mail doesn’t work!” e-mail opens the e-mail details window, like so:
If we click on the User drop down and select ‘Jonathan Huss’ and then click ‘Assign’:
The window will close and a new Support Ticket will be created for me:
If we go over to the SharePoint site and add a new Announcement:
We can see that it’s automatically added to the Announcement quadrant:
Conclusion
So there you have it. A single application that utilizes many of the available Office 365 and Azure resources available. Office 365, Azure, and the cloud are exceedingly powerful tools that provide all kinds of functionality that can be utilized to enhance almost any project if we can just understand how to use them. This is a fantastic time to be a developer!
The Help Desk Demo
- The Help Desk demo, Part 1 – Creating the project
- The Help Desk demo, Part 2 – Azure Active Directory
- The Help Desk demo, Part 3 – Authentication
- The Help Desk demo, Part 4 – Microsoft Graph
- The Help Desk demo, Part 5 – SQL Azure
- The Help Desk demo, Part 6 – SharePoint
- The Help Desk demo, Part 7 – Wiring it all up
- The Help Desk demo, Part 8 – Deploying to Azure
The entire source code for the Help Desk demo can be found here https://github.com/OfficeDev/PnP/tree/dev/Solutions/BusinessApps.HelpDesk/, in the Office 365 Dev PnP GitHub repository.
Good post! Thanks!