예제 #1
0
        public async Task <IActionResult> SaveConfigurationsAsync([FromBody] CardConfigurationEntity configurationEntity)
        {
            try
            {
                string user = this.HttpContext.User.Identity.Name;
                if (!this.validUsers.Contains(user))
                {
                    return(this.Unauthorized());
                }

                configurationEntity.CardId    = Guid.NewGuid().ToString();
                configurationEntity.CreatedOn = DateTime.UtcNow;
                configurationEntity.CreatedByUserPrincipalName = user;
                configurationEntity.CreatedByObjectId          = this.GetId();
                configurationEntity.TeamId = Utility.ParseTeamIdFromDeepLink(configurationEntity.TeamLink);

                var result = await this.configurationStorageProvider.StoreOrUpdateEntityAsync(configurationEntity);

                if (result == null)
                {
                    this.logger.LogInformation("Error in saving configurations " + configurationEntity.CreatedByObjectId);
                    return(this.StatusCode(StatusCodes.Status500InternalServerError));
                }

                this.logger.LogInformation("Configurations saved successfully " + configurationEntity.CreatedByObjectId);
                return(this.Ok());
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error in saving configurations");
                throw;
            }
        }
예제 #2
0
        /// <summary>
        /// Store or update card configuration entity in table storage.
        /// </summary>
        /// <param name="configurationEntity">Represents configuration entity used for storage and retrieval.</param>
        /// <returns><see cref="Task"/> that represents configuration entity is saved or updated.</returns>
        public async Task <CardConfigurationEntity> StoreOrUpdateEntityAsync(CardConfigurationEntity configurationEntity)
        {
            await this.EnsureInitializedAsync();

            TableOperation addOrUpdateOperation = TableOperation.InsertOrReplace(configurationEntity);
            var            result = await this.CloudTable.ExecuteAsync(addOrUpdateOperation);

            return(result.Result as CardConfigurationEntity);
        }
        /// <summary>
        /// Handle when a message is addressed to the bot in personal scope.
        /// </summary>
        /// <param name="message">Message activity of bot.</param>
        /// <param name="turnContext">The turn context.</param>
        /// <param name="telemetryClient">The Application Insights telemetry client. </param>
        /// <param name="logger">Sends logs to the Application Insights service.</param>
        /// <param name="cardConfigurationStorageProvider">Provider to search card configuration details in Azure Table Storage.</param>
        /// <param name="environment">Hosting environment.</param>
        /// <param name="ticketGenerateStorageProvider">Provider to get ticket id to Azure Table Storage.</param>
        /// <param name="ticketDetailStorageProvider">Provider to store ticket details to Azure Table Storage.</param>
        /// <param name="microsoftAppCredentials">Microsoft Application credentials for Bot/ME.</param>
        /// <param name="appBaseUrl">Represents the Application base Uri.</param>
        /// <param name="localizer">The current cultures' string localizer.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns> A task that represents the work queued to execute for user message activity to bot.</returns>
        internal static async Task OnMessageActivityInPersonalChatAsync(
            IMessageActivity message,
            ITurnContext <IMessageActivity> turnContext,
            TelemetryClient telemetryClient,
            ILogger <RemoteSupportActivityHandler> logger,
            ICardConfigurationStorageProvider cardConfigurationStorageProvider,
            IHostingEnvironment environment,
            ITicketIdGeneratorStorageProvider ticketGenerateStorageProvider,
            ITicketDetailStorageProvider ticketDetailStorageProvider,
            MicrosoftAppCredentials microsoftAppCredentials,
            string appBaseUrl,
            IStringLocalizer <Strings> localizer,
            CancellationToken cancellationToken)
        {
            if (!string.IsNullOrEmpty(message.ReplyToId) && message.Value != null && ((JObject)message.Value).HasValues)
            {
                telemetryClient.TrackTrace("Card submitted in 1:1 chat.");
                await OnAdaptiveCardSubmitInPersonalChatAsync(message: message, turnContext : turnContext, ticketGenerateStorageProvider : ticketGenerateStorageProvider, ticketDetailStorageProvider : ticketDetailStorageProvider, cardConfigurationStorageProvider : cardConfigurationStorageProvider, microsoftAppCredentials : microsoftAppCredentials, logger : logger, appBaseUrl : appBaseUrl, environment : environment, localizer : localizer, cancellationToken : cancellationToken);

                return;
            }

            string text = (turnContext.Activity.Text ?? string.Empty).Trim().ToUpperInvariant();

            switch (text)
            {
            case Constants.NewRequestAction:
                logger.LogInformation("New request action called.");
                CardConfigurationEntity cardTemplateJson = await cardConfigurationStorageProvider.GetConfigurationAsync();

                IMessageActivity newTicketActivity = MessageFactory.Attachment(TicketCard.GetNewTicketCard(cardTemplateJson, localizer));
                await turnContext.SendActivityAsync(newTicketActivity);

                break;

            case Constants.NoCommand:
                return;

            default:
                if (turnContext.Activity.Attachments == null || turnContext.Activity.Attachments.Count == 0)
                {
                    // In case of ME when user clicks on closed or active requests the bot posts adaptive card of request details we don't have to consider this as invalid command.
                    logger.LogInformation("Unrecognized input in End User.");
                    await turnContext.SendActivityAsync(MessageFactory.Attachment(WelcomeCard.GetCard(appBaseUrl, localizer)));
                }

                break;
            }
        }
        /// <summary>
        /// Returns the Adaptive card item element {Id, display name} mapping present in the Azure table storage by CardId
        /// </summary>
        /// <param name="cardId">Unique identifier of the card configuration.</param>
        /// <returns>A <see cref="Task{TResult}"/>configuration details.</returns>
        public async Task <Dictionary <string, string> > GetCardItemElementMappingAsync(string cardId)
        {
            Dictionary <string, string> cardElementMapping = new Dictionary <string, string>();
            CardConfigurationEntity     configuration      = await this.GetConfigurationsByCardIdAsync(cardId);

            var cardTemplates = JsonConvert.DeserializeObject <List <JObject> >(configuration?.CardTemplate);

            foreach (var template in cardTemplates)
            {
                var templateMapping = template.ToObject <AdaptiveCardPlaceHolderMapper>();
                cardElementMapping.Add(templateMapping.Id, templateMapping.DisplayName);
            }

            return(cardElementMapping);
        }
예제 #5
0
        /// <summary>
        /// Handle when a message is addressed to the bot in personal scope.
        /// </summary>
        /// <param name="message">Message activity of bot.</param>
        /// <param name="turnContext">The turn context.</param>
        /// <param name="logger">Sends logs to the Application Insights service.</param>
        /// <param name="cardConfigurationStorageProvider">Provider to search card configuration details in Azure Table Storage.</param>
        /// <param name="ticketGenerateStorageProvider">Provider to get ticket id to Azure Table Storage.</param>
        /// <param name="ticketDetailStorageProvider">Provider to store ticket details to Azure Table Storage.</param>
        /// <param name="microsoftAppCredentials">Microsoft Application credentials for Bot/ME.</param>
        /// <param name="appBaseUrl">Represents the Application base Uri.</param>
        /// <param name="localizer">The current cultures' string localizer.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns> A task that represents the work queued to execute for user message activity to bot.</returns>
        internal static async Task OnMessageActivityInPersonalChatAsync(
            IMessageActivity message,
            ITurnContext <IMessageActivity> turnContext,
            ILogger logger,
            ICardConfigurationStorageProvider cardConfigurationStorageProvider,
            ITicketIdGeneratorStorageProvider ticketGenerateStorageProvider,
            ITicketDetailStorageProvider ticketDetailStorageProvider,
            MicrosoftAppCredentials microsoftAppCredentials,
            string appBaseUrl,
            IStringLocalizer <Strings> localizer,
            CancellationToken cancellationToken)
        {
            if (!string.IsNullOrEmpty(message.ReplyToId) && message.Value != null && ((JObject)message.Value).HasValues)
            {
                logger.LogInformation("Card submitted in 1:1 chat.");
                await OnAdaptiveCardSubmitInPersonalChatAsync(message, turnContext, ticketGenerateStorageProvider, ticketDetailStorageProvider, cardConfigurationStorageProvider, microsoftAppCredentials, logger, appBaseUrl, localizer, cancellationToken);

                return;
            }

            string text = (turnContext.Activity.Text ?? string.Empty).Trim().ToUpperInvariant();

            if (text.Equals(localizer.GetString("BotCommandNewRequest"), StringComparison.CurrentCultureIgnoreCase))
            {
                logger.LogInformation("New request action called.");
                CardConfigurationEntity cardTemplateJson = await cardConfigurationStorageProvider.GetConfigurationAsync();

                IMessageActivity newTicketActivity = MessageFactory.Attachment(TicketCard.GetNewTicketCard(cardTemplateJson, localizer));
                await turnContext.SendActivityAsync(newTicketActivity);
            }
            else if (text.Equals(localizer.GetString("No").ToString(), StringComparison.CurrentCultureIgnoreCase))
            {
                return;
            }
            else
            {
                if (turnContext.Activity.Attachments == null || turnContext.Activity.Attachments.Count == 0)
                {
                    // In case of ME when user clicks on closed or active requests the bot posts adaptive card of request details we don't have to consider this as invalid command.
                    logger.LogInformation("Unrecognized input in End User.");
                    await turnContext.SendActivityAsync(MessageFactory.Attachment(WelcomeCard.GetCard(appBaseUrl, localizer)));
                }
            }
        }
예제 #6
0
        /// <summary>
        /// Method Handle adaptive card submit in 1:1 chat and Send new ticket details to SME team.
        /// </summary>
        /// <param name="message">Message activity of bot.</param>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="ticketGenerateStorageProvider">Provider to get ticket id to Azure Table Storage.</param>
        /// <param name="ticketDetailStorageProvider">Provider to store ticket details to Azure Table Storage.</param>
        /// <param name="cardConfigurationStorageProvider">Provider to search card configuration details in Azure Table Storage.</param>
        /// <param name="microsoftAppCredentials">Microsoft Application credentials for Bot/ME.</param>
        /// <param name="logger">Sends logs to the Application Insights service.</param>
        /// <param name="appBaseUrl">Represents the Application base Uri.</param>
        /// <param name="localizer">The current cultures' string localizer.</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        /// <returns>A task that handles submit action in 1:1 chat.</returns>
        internal static async Task OnAdaptiveCardSubmitInPersonalChatAsync(
            IMessageActivity message,
            ITurnContext <IMessageActivity> turnContext,
            ITicketIdGeneratorStorageProvider ticketGenerateStorageProvider,
            ITicketDetailStorageProvider ticketDetailStorageProvider,
            ICardConfigurationStorageProvider cardConfigurationStorageProvider,
            MicrosoftAppCredentials microsoftAppCredentials,
            ILogger logger,
            string appBaseUrl,
            IStringLocalizer <Strings> localizer,
            CancellationToken cancellationToken)
        {
            IMessageActivity endUserUpdateCard;

            switch (message.Text.ToUpperInvariant())
            {
            case Constants.SendRequestAction:
                TicketDetail newTicketDetail = JsonConvert.DeserializeObject <TicketDetail>(message.Value?.ToString());
                if (TicketHelper.ValidateRequestDetail(newTicketDetail))
                {
                    AdaptiveCardAction cardDetail = ((JObject)message.Value).ToObject <AdaptiveCardAction>();
                    logger.LogInformation("Adding new request with additional details.");
                    var ticketTd = await ticketGenerateStorageProvider.GetTicketIdAsync();

                    // Update new request with additional details.
                    var userDetails = await GetUserDetailsInPersonalChatAsync(turnContext, cancellationToken);

                    newTicketDetail.TicketId = ticketTd.ToString(CultureInfo.InvariantCulture);
                    newTicketDetail          = TicketHelper.GetNewTicketDetails(turnContext: turnContext, ticketDetail: newTicketDetail, ticketAdditionalDetails: message.Value?.ToString(), cardId: cardDetail.CardId, member: userDetails);
                    bool result = await ticketDetailStorageProvider.UpsertTicketAsync(newTicketDetail);

                    if (!result)
                    {
                        logger.LogError("Error in storing new ticket details in table storage.");
                        await turnContext.SendActivityAsync(localizer.GetString("AzureStorageErrorText"));

                        return;
                    }

                    logger.LogInformation("New request created with ticket Id:" + newTicketDetail.TicketId);

                    // Get card item element mappings
                    var carditemElementMapping = await cardConfigurationStorageProvider.GetCardItemElementMappingAsync(cardDetail?.CardId);

                    endUserUpdateCard = MessageFactory.Attachment(TicketCard.GetTicketDetailsForPersonalChatCard(carditemElementMapping, newTicketDetail, localizer, false));
                    await CardHelper.SendRequestCardToSMEChannelAsync(turnContext : turnContext, ticketDetail : newTicketDetail, logger : logger, ticketDetailStorageProvider : ticketDetailStorageProvider, applicationBasePath : appBaseUrl, cardElementMapping : carditemElementMapping, localizer, teamId : cardDetail?.TeamId, microsoftAppCredentials : microsoftAppCredentials, cancellationToken : cancellationToken);

                    await CardHelper.UpdateRequestCardForEndUserAsync(turnContext, endUserUpdateCard);

                    await turnContext.SendActivityAsync(MessageFactory.Text(localizer.GetString("EndUserNotificationText", newTicketDetail.TicketId)));
                }
                else
                {
                    // Update card with validation message.
                    newTicketDetail.AdditionalProperties = CardHelper.ValidateAdditionalTicketDetails(message.Value?.ToString(), timeSpan: turnContext.Activity.LocalTimestamp.Value.Offset);
                    CardConfigurationEntity cardTemplateJson = await cardConfigurationStorageProvider.GetConfigurationAsync();

                    endUserUpdateCard = MessageFactory.Attachment(TicketCard.GetNewTicketCard(cardConfiguration: cardTemplateJson, localizer: localizer, showValidationMessage: true, ticketDetail: newTicketDetail));
                    await CardHelper.UpdateRequestCardForEndUserAsync(turnContext, endUserUpdateCard);
                }

                break;

            case Constants.WithdrawRequestAction:
                var payload = ((JObject)message.Value).ToObject <AdaptiveCardAction>();
                endUserUpdateCard = MessageFactory.Attachment(WithdrawCard.GetCard(payload.PostedValues, localizer));

                // Get the ticket from the data store.
                TicketDetail ticketDetail = await ticketDetailStorageProvider.GetTicketAsync(payload.PostedValues);

                if (ticketDetail.TicketStatus == (int)TicketState.Kapatılmış)
                {
                    await turnContext.SendActivityAsync(localizer.GetString("WithdrawErrorMessage"));

                    return;
                }

                ticketDetail.LastModifiedByName     = message.From.Name;
                ticketDetail.LastModifiedByObjectId = message.From.AadObjectId;
                ticketDetail.TicketStatus           = (int)TicketState.Vazgeçilmiş;
                bool success = await ticketDetailStorageProvider.UpsertTicketAsync(ticketDetail);

                if (!success)
                {
                    logger.LogError("Error in updating ticket details in table storage.");
                    await turnContext.SendActivityAsync(localizer.GetString("AzureStorageErrorText"));

                    return;
                }

                logger.LogInformation("Withdrawn the ticket:" + ticketDetail.TicketId);
                IMessageActivity smeWithdrawNotification = MessageFactory.Text(localizer.GetString("SmeWithdrawNotificationText", ticketDetail.RequesterName));
                var itemElementMapping = await cardConfigurationStorageProvider.GetCardItemElementMappingAsync(ticketDetail?.CardId);

                await CardHelper.UpdateSMECardAsync(turnContext, ticketDetail, smeWithdrawNotification, appBaseUrl, itemElementMapping, localizer, logger, cancellationToken);

                await CardHelper.UpdateRequestCardForEndUserAsync(turnContext, endUserUpdateCard);

                break;
            }
        }
        /// <summary>
        /// Get the create new ticket card.
        /// </summary>
        /// <param name="cardConfiguration">Card configuration.</param>
        /// <param name="localizer">The current cultures' string localizer.</param>
        /// <param name="showValidationMessage">Represents whether to show validation message or not.</param>
        /// <param name="ticketDetail"> Information of the ticket which is being created.</param>
        /// <returns>Returns an attachment of new ticket.</returns>
        public static Attachment GetNewTicketCard(CardConfigurationEntity cardConfiguration, IStringLocalizer <Strings> localizer, bool showValidationMessage = false, TicketDetail ticketDetail = null)
        {
            cardConfiguration = cardConfiguration ?? throw new ArgumentNullException(nameof(cardConfiguration));

            string issueTitle       = string.Empty;
            string issueDescription = string.Empty;

            var  dynamicElements           = new List <AdaptiveElement>();
            var  ticketAdditionalFields    = new List <AdaptiveElement>();
            bool showTitleValidation       = false;
            bool showDescriptionValidation = false;
            bool showDateValidation        = false;

            if (showValidationMessage)
            {
                ticketDetail = ticketDetail ?? throw new ArgumentNullException(nameof(ticketDetail));
                if (string.IsNullOrWhiteSpace(ticketDetail.Title))
                {
                    showTitleValidation = true;
                }
                else
                {
                    issueTitle = ticketDetail.Title;
                }

                if (string.IsNullOrWhiteSpace(ticketDetail.Description))
                {
                    showDescriptionValidation = true;
                }
                else
                {
                    issueDescription = ticketDetail.Description;
                }
            }

            ticketAdditionalFields = CardHelper.ConvertToAdaptiveCard(localizer, cardConfiguration.CardTemplate, showDateValidation);

            dynamicElements.AddRange(new List <AdaptiveElement>
            {
                new AdaptiveTextBlock
                {
                    Text   = localizer.GetString("NewRequestTitle"),
                    Weight = AdaptiveTextWeight.Bolder,
                    Size   = AdaptiveTextSize.Large,
                },
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("TellUsAboutProblemText"),
                    Spacing = AdaptiveSpacing.Small,
                },
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("TitleDisplayText"),
                    Spacing = AdaptiveSpacing.Medium,
                },
                new AdaptiveTextInput()
                {
                    Id          = "Title",
                    MaxLength   = 100,
                    Placeholder = localizer.GetString("TitlePlaceHolderText"),
                    Spacing     = AdaptiveSpacing.Small,
                    Value       = issueTitle,
                },
                new AdaptiveTextBlock()
                {
                    Text      = localizer.GetString("TitleValidationText"),
                    Spacing   = AdaptiveSpacing.None,
                    IsVisible = showTitleValidation,
                    Color     = AdaptiveTextColor.Attention,
                },
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("DescriptionText"),
                    Spacing = AdaptiveSpacing.Medium,
                },
                new AdaptiveTextInput()
                {
                    Id          = "Description",
                    MaxLength   = 500,
                    IsMultiline = true,
                    Placeholder = localizer.GetString("DesciptionPlaceHolderText"),
                    Spacing     = AdaptiveSpacing.Small,
                    Value       = issueDescription,
                },
                new AdaptiveTextBlock()
                {
                    Text      = localizer.GetString("DescriptionValidationText"),
                    Spacing   = AdaptiveSpacing.None,
                    IsVisible = showDescriptionValidation,
                    Color     = AdaptiveTextColor.Attention,
                },
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("RequestTypeText"),
                    Spacing = AdaptiveSpacing.Medium,
                },
                new AdaptiveChoiceSetInput
                {
                    Choices = new List <AdaptiveChoice>
                    {
                        new AdaptiveChoice
                        {
                            Title = localizer.GetString("NormalText"),
                            Value = Constants.NormalString,
                        },
                        new AdaptiveChoice
                        {
                            Title = localizer.GetString("UrgentText"),
                            Value = Constants.UrgentString,
                        },
                    },
                    Id    = "RequestType",
                    Value = !string.IsNullOrEmpty(ticketDetail?.RequestType) ? ticketDetail?.RequestType : Constants.NormalString,
                    Style = AdaptiveChoiceInputStyle.Expanded,
                },
            });

            dynamicElements.AddRange(ticketAdditionalFields);

            AdaptiveCard ticketDetailsPersonalChatCard = new AdaptiveCard(Constants.AdaptiveCardVersion)
            {
                Body    = dynamicElements,
                Actions = new List <AdaptiveAction>
                {
                    new AdaptiveSubmitAction
                    {
                        Title = localizer.GetString("SendRequestButtonText"),
                        Id    = "SendRequest",
                        Data  = new AdaptiveCardAction
                        {
                            MsteamsCardAction = new CardAction
                            {
                                Type = Constants.MessageBackActionType,
                                Text = Constants.SendRequestAction,
                            },
                            CardId = cardConfiguration?.CardId,
                            TeamId = cardConfiguration?.TeamId,
                        },
                    },
                },
            };

            return(new Attachment
            {
                ContentType = AdaptiveCard.ContentType,
                Content = ticketDetailsPersonalChatCard,
            });
        }
        /// <summary>
        /// Gets Edit card for task module.
        /// </summary>
        /// <param name="ticketDetail">Ticket details from user.</param>
        /// <param name="cardConfiguration">Card configuration.</param>
        /// <param name="localizer">The current cultures' string localizer.</param>
        /// <param name="existingTicketDetail">Existing ticket details.</param>
        /// <returns>Returns an attachment of edit card.</returns>
        public static Attachment GetEditRequestCard(TicketDetail ticketDetail, CardConfigurationEntity cardConfiguration, IStringLocalizer <Strings> localizer, TicketDetail existingTicketDetail = null)
        {
            cardConfiguration = cardConfiguration ?? throw new ArgumentNullException(nameof(cardConfiguration));
            ticketDetail      = ticketDetail ?? throw new ArgumentNullException(nameof(ticketDetail));

            string issueTitle                = string.Empty;
            string issueDescription          = string.Empty;
            var    dynamicElements           = new List <AdaptiveElement>();
            var    ticketAdditionalFields    = new List <AdaptiveElement>();
            bool   showTitleValidation       = false;
            bool   showDescriptionValidation = false;
            bool   showDateValidation        = false;

            if (string.IsNullOrWhiteSpace(ticketDetail.Title))
            {
                showTitleValidation = true;
            }
            else
            {
                issueTitle = ticketDetail.Title;
            }

            if (string.IsNullOrWhiteSpace(ticketDetail.Description))
            {
                showDescriptionValidation = true;
            }
            else
            {
                issueDescription = ticketDetail.Description;
            }

            if (ticketDetail.IssueOccuredOn == null || DateTimeOffset.Compare(ticketDetail.IssueOccuredOn, DateTime.Today) > 0 || string.IsNullOrEmpty(ticketDetail.IssueOccuredOn.ToString(CultureInfo.InvariantCulture)))
            {
                showDateValidation = true;
            }
            else if (existingTicketDetail != null && DateTimeOffset.Compare(ticketDetail.IssueOccuredOn, existingTicketDetail.IssueOccuredOn) > 0)
            {
                showDateValidation = true;
            }

            var ticketAdditionalDetails = JsonConvert.DeserializeObject <Dictionary <string, string> >(ticketDetail.AdditionalProperties);

            ticketAdditionalFields = CardHelper.ConvertToAdaptiveCard(localizer, cardConfiguration.CardTemplate, showDateValidation, ticketAdditionalDetails);

            dynamicElements.AddRange(new List <AdaptiveElement>
            {
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("TitleDisplayText"),
                    Spacing = AdaptiveSpacing.Medium,
                },
                new AdaptiveTextInput()
                {
                    Id          = "Title",
                    MaxLength   = 100,
                    Placeholder = localizer.GetString("TitlePlaceHolderText"),
                    Spacing     = AdaptiveSpacing.Small,
                    Value       = issueTitle,
                },
                new AdaptiveTextBlock()
                {
                    Text      = localizer.GetString("TitleValidationText"),
                    Spacing   = AdaptiveSpacing.None,
                    IsVisible = showTitleValidation,
                    Color     = AdaptiveTextColor.Attention,
                },
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("DescriptionText"),
                    Spacing = AdaptiveSpacing.Medium,
                },
                new AdaptiveTextInput()
                {
                    Id          = "Description",
                    MaxLength   = 500,
                    IsMultiline = true,
                    Placeholder = localizer.GetString("DesciptionPlaceHolderText"),
                    Spacing     = AdaptiveSpacing.Small,
                    Value       = issueDescription,
                },
                new AdaptiveTextBlock()
                {
                    Text      = localizer.GetString("DescriptionValidationText"),
                    Spacing   = AdaptiveSpacing.None,
                    IsVisible = showDescriptionValidation,
                    Color     = AdaptiveTextColor.Attention,
                },
                new AdaptiveTextBlock()
                {
                    Text    = localizer.GetString("RequestTypeText"),
                    Spacing = AdaptiveSpacing.Medium,
                },
                new AdaptiveChoiceSetInput
                {
                    Choices = new List <AdaptiveChoice>
                    {
                        new AdaptiveChoice
                        {
                            Title = localizer.GetString("NormalText"),
                            Value = localizer.GetString("NormalText"),
                        },
                        new AdaptiveChoice
                        {
                            Title = localizer.GetString("UrgentText"),
                            Value = localizer.GetString("UrgentText"),
                        },
                    },
                    Id    = "RequestType",
                    Value = !string.IsNullOrEmpty(ticketDetail?.RequestType) ? ticketDetail?.RequestType : localizer.GetString("NormalText"),
                    Style = AdaptiveChoiceInputStyle.Expanded,
                },
            });

            dynamicElements.AddRange(ticketAdditionalFields);

            AdaptiveCard ticketDetailsPersonalChatCard = new AdaptiveCard(Constants.AdaptiveCardVersion)
            {
                Body    = dynamicElements,
                Actions = new List <AdaptiveAction>
                {
                    new AdaptiveSubmitAction
                    {
                        Title = localizer.GetString("UpdateActionText"),
                        Id    = "UpdateRequest",
                        Data  = new AdaptiveCardAction
                        {
                            Command  = Constants.UpdateRequestAction,
                            TeamId   = cardConfiguration?.TeamId,
                            TicketId = ticketDetail.TicketId,
                            CardId   = ticketDetail.CardId,
                        },
                    },
                    new AdaptiveSubmitAction
                    {
                        Title = localizer.GetString("CancelButtonText"),
                        Id    = "Cancel",
                        Data  = new AdaptiveCardAction
                        {
                            Command = Constants.CancelCommand,
                        },
                    },
                },
            };

            return(new Attachment
            {
                ContentType = AdaptiveCard.ContentType,
                Content = ticketDetailsPersonalChatCard,
            });
        }