// A sample mapping from ServiceNow response types to Bot Framework concepts, in many cases this will need to be adapted to a // Bot owners specific requirements and styling. private async Task HandleContentEvent(ServiceNowResponseMessage responseMessage) { foreach (var item in responseMessage.body) { IMessageActivity responseActivity; // Map ServiceNow UX controls to Bot Framework concepts. This will need refinement as broader experiences are used but this covers a broad range of out-of-box ServiceNow response types. switch (item.uiType) { case "TopicPickerControl": case "ItemPicker": case "Picker": // Map the picker concept to a basic HeroCard with buttons List <CardAction> cardActions = new List <CardAction>(); foreach (var option in item.options) { cardActions.Add(new CardAction("imBack", option.description ?? option.label, value: option.label)); } var pickerHeroCard = new HeroCard(buttons: cardActions); responseActivity = MessageFactory.Attachment(pickerHeroCard.ToAttachment()); responseActivity.AsMessageActivity().Text = item.promptMsg ?? item.label; break; case "DefaultPicker": // Map the picker concept to a basic HeroCard with buttons List <CardAction> defaultPickerActions = new List <CardAction>(); foreach (var option in item.options) { defaultPickerActions.Add(new CardAction("imBack", option.description ?? option.label, value: option.label)); } var defaultPickerCard = new HeroCard(buttons: defaultPickerActions); responseActivity = MessageFactory.Attachment(defaultPickerCard.ToAttachment()); break; case "GroupedPartsOutputControl": responseActivity = MessageFactory.Text(item.header); responseActivity.AttachmentLayout = "carousel"; responseActivity.Attachments = new List <Microsoft.Bot.Schema.Attachment>(); foreach (var action in item.values) { var cardAction = new CardAction("openUrl", action.label, value: action.action); var card = new HeroCard(action.label, null, action.description, null, null, cardAction); responseActivity.Attachments.Add(card.ToAttachment()); } break; case "OutputHtml": // We can't render HTML inside of conversations. responseActivity = MessageFactory.Text(StripTags(item.value)); break; case "Boolean": List <CardAction> booleanCardActions = new List <CardAction>(); booleanCardActions.Add(new CardAction("imBack", title: "Yes", displayText: "Yes", value: "true")); booleanCardActions.Add(new CardAction("imBack", title: "No", displayText: "Yes", value: "false")); var booleanHeroCard = new HeroCard(buttons: booleanCardActions); responseActivity = MessageFactory.Attachment(booleanHeroCard.ToAttachment()); responseActivity.AsMessageActivity().Text = item.promptMsg ?? item.label; break; case "OutputText": responseActivity = MessageFactory.Text(item.value ?? item.label); break; case "OutputImage": var cardImages = new List <CardImage>(); cardImages.Add(new CardImage(url: item.value)); var imageHeroCard = new HeroCard(images: cardImages); responseActivity = MessageFactory.Attachment(imageHeroCard.ToAttachment()); break; case "OutputLink": var linkHeroCard = new HeroCard(buttons: new List <CardAction> { new CardAction("openUrl", item.value) }); responseActivity = MessageFactory.Attachment(linkHeroCard.ToAttachment()); responseActivity.AsMessageActivity().Text = item.promptMsg ?? item.label; break; default: responseActivity = MessageFactory.Text(item.value ?? item.label); break; } if (await ConversationHandoffRecordMap.GetByRemoteConversationId(responseMessage.clientSessionId) is ServiceNowHandoffRecord handoffRecord) { if (!handoffRecord.ConversationRecord.IsClosed) { MicrosoftAppCredentials.TrustServiceUrl(handoffRecord.ConversationReference.ServiceUrl); await(_adapter).ContinueConversationAsync( _credentials.MsAppId, handoffRecord.ConversationReference, (turnContext, cancellationToken) => turnContext.SendActivityAsync(responseActivity, cancellationToken), default); var traceActivity = Activity.CreateTraceActivity( "ServiceNowVirtualAgent", $"Response from ServiceNow Virtual Agent received (Id:{responseMessage.requestId})", label: "ServiceNowHandoff->Response from ServiceNow Virtual Agent"); await(_adapter).ContinueConversationAsync( _credentials.MsAppId, handoffRecord.ConversationReference, (turnContext, cancellationToken) => turnContext.SendActivityAsync(traceActivity, cancellationToken), default); } } else { // The bot has no record of this conversation, this should not happen throw new Exception("Cannot find conversation"); } } }
// A sample mapping from ServiceNow response types to Bot Framework concepts, in many cases this will need to be adapted to a // Bot owners specific requirements and styling. private async Task HandleContentEvent(ServiceNowResponseMessage responseMessage) { int VAOptionsCount = 0; foreach (var item in responseMessage.body) { IMessageActivity responseActivity; // Map ServiceNow UX controls to Bot Framework concepts. This will need refinement as broader experiences are used but this covers a broad range of out-of-box ServiceNow response types. switch (item.uiType) { case "TopicPickerControl": case "ItemPicker": case "Picker": if (item.options.All(o => !string.IsNullOrEmpty(o.attachment))) { // Map the picker concept to a HeroCard carousel responseActivity = MessageFactory.Text(item.label); responseActivity.AttachmentLayout = "carousel"; responseActivity.Attachments = new List <Microsoft.Bot.Schema.Attachment>(); foreach (var option in item.options) { var card = new HeroCard( subtitle: option.description, images: new List <CardImage>() { new CardImage($"https://{_credentials.ServiceNowTenant}/{option.attachment}") }, buttons: new List <CardAction> { new CardAction("imBack", option.label, value: option.label) }); responseActivity.Attachments.Add(card.ToAttachment()); } } else { List <CardAction> cardActions = new List <CardAction>(); foreach (var option in item.options) { VAOptionsCount = VAOptionsCount + 1; cardActions.Add(new CardAction("imBack", option.description ?? option.label, value: option.label)); if (VAOptionsCount <= item.options.Count()) { if (VAOptionsCount == 50) { break; } } } var pickerHeroCard = new HeroCard(text: item.promptMsg ?? item.label, buttons: cardActions); responseActivity = MessageFactory.Attachment(pickerHeroCard.ToAttachment()); } break; case "DefaultPicker": // Map the picker concept to a basic HeroCard with buttons List <CardAction> defaultPickerActions = new List <CardAction>(); foreach (var option in item.options) { VAOptionsCount = VAOptionsCount + 1; defaultPickerActions.Add(new CardAction("imBack", option.description ?? option.label, value: option.label)); if (VAOptionsCount <= item.options.Count()) { if (VAOptionsCount == 50) { break; } } } var defaultPickerCard = new HeroCard(buttons: defaultPickerActions); responseActivity = MessageFactory.Attachment(defaultPickerCard.ToAttachment()); break; case "GroupedPartsOutputControl": responseActivity = MessageFactory.Text(item.header); responseActivity.AttachmentLayout = "carousel"; responseActivity.Attachments = new List <Microsoft.Bot.Schema.Attachment>(); foreach (var action in item.values) { var card = new HeroCard(subtitle: action.description, buttons: new List <CardAction> { new CardAction("openUrl", action.label ?? action.action, value: action.action) }); if (VAOptionsCount <= item.values.Count()) { if (VAOptionsCount == 11) { break; } responseActivity.Attachments.Add(card.ToAttachment()); } } break; case "Boolean": List <CardAction> booleanCardActions = new List <CardAction>(); booleanCardActions.Add(new CardAction("imBack", title: "Yes", displayText: "Yes", value: "true")); booleanCardActions.Add(new CardAction("imBack", title: "No", displayText: "Yes", value: "false")); var booleanHeroCard = new HeroCard(text: item.promptMsg ?? item.label, buttons: booleanCardActions); responseActivity = MessageFactory.Attachment(booleanHeroCard.ToAttachment()); break; case "OutputText": responseActivity = MessageFactory.Text(item.value?.ToString() ?? item.label); break; case "OutputImage": var cardImages = new List <CardImage>(); cardImages.Add(new CardImage(url: $"{_credentials.ServiceNowTenant}{item.value}")); var imageHeroCard = new HeroCard(images: cardImages); responseActivity = MessageFactory.Attachment(imageHeroCard.ToAttachment()); break; case "OutputLink": var bodyValue = item.value as BodyValue; var linkHeroCard = new HeroCard(text: item.header, buttons: new List <CardAction> { new CardAction("openUrl", item.label, value: bodyValue.action) }); responseActivity = MessageFactory.Attachment(linkHeroCard.ToAttachment()); break; case "OutputCard": var cardData = JObject.Parse(item.data.ToString()); var items = new List <AdaptiveColumnSet>(); var titleColumnSet = new AdaptiveColumnSet { Columns = new List <AdaptiveColumn>() { new AdaptiveColumn { Items = new List <AdaptiveElement>() { new AdaptiveTextBlock(cardData["title"]?.ToString()) { Size = AdaptiveTextSize.Medium, Weight = AdaptiveTextWeight.Bolder } } }, new AdaptiveColumn { Items = new List <AdaptiveElement>() { new AdaptiveTextBlock(cardData["subtitle"]?.ToString()) { Size = AdaptiveTextSize.Medium } } } } }; items.Add(titleColumnSet); foreach (var field in cardData["fields"]) { var columnSet = new AdaptiveColumnSet { Columns = new List <AdaptiveColumn>() { new AdaptiveColumn { Items = new List <AdaptiveElement>() { new AdaptiveTextBlock(field["fieldLabel"]?.ToString()) { Weight = AdaptiveTextWeight.Bolder } } }, new AdaptiveColumn { Items = new List <AdaptiveElement>() { new AdaptiveTextBlock(field["fieldValue"]?.ToString()) { Wrap = true } } } } }; items.Add(columnSet); } var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)) { Body = new List <AdaptiveElement>() }; adaptiveCard.Body.AddRange(items); responseActivity = MessageFactory.Attachment(new Attachment { ContentType = AdaptiveCard.ContentType, Content = JObject.FromObject(adaptiveCard), }); break; case "DateTime": var dateTimeCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2)) { Body = new List <AdaptiveElement>() { new AdaptiveContainer() { Items = new List <AdaptiveElement>() { new AdaptiveTextBlock() { Text = item.label, Wrap = true }, new AdaptiveDateInput() { Id = "dateVal", Value = DateTime.UtcNow.ToString("MM-dd-yyyy") }, new AdaptiveTimeInput() { Id = "timeVal" } } } }, Actions = new List <AdaptiveAction>() { // Using a SubmitAction with an msteams messageBack object to show display text // from the user while sending the date and time inputs as data new AdaptiveSubmitAction() { Title = "Submit", DataJson = "{\r\n\"msteams\": {\r\n\"type\": \"messageBack\",\r\n\"displayText\": \"Datetime submitted\"\r\n}\r\n}" } } }; responseActivity = MessageFactory.Attachment(new Attachment { ContentType = AdaptiveCard.ContentType, Content = JObject.FromObject(dateTimeCard), }); break; default: responseActivity = MessageFactory.Text(item.value?.ToString() ?? item.label); break; } if (await ConversationHandoffRecordMap.GetByRemoteConversationId(responseMessage.clientSessionId) is ServiceNowHandoffRecord handoffRecord) { if (!handoffRecord.ConversationRecord.IsClosed) { MicrosoftAppCredentials.TrustServiceUrl(handoffRecord.ConversationReference.ServiceUrl); await(_adapter).ContinueConversationAsync( _credentials.MsAppId, handoffRecord.ConversationReference, (turnContext, cancellationToken) => turnContext.SendActivityAsync(responseActivity, cancellationToken), default); var traceActivity = Activity.CreateTraceActivity( "ServiceNowVirtualAgent", $"Response from ServiceNow Virtual Agent received (Id:{responseMessage.requestId})", label: "ServiceNowHandoff->Response from ServiceNow Virtual Agent"); await(_adapter).ContinueConversationAsync( _credentials.MsAppId, handoffRecord.ConversationReference, (turnContext, cancellationToken) => turnContext.SendActivityAsync(traceActivity, cancellationToken), default); } } else { // The bot has no record of this conversation, this should not happen throw new Exception("Cannot find conversation"); } } }