/// <summary> /// Send event card /// </summary> /// <param name="eventMessages">List of EventMessage</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task SendEventCard(List <EventMessage> eventMessages) { var tasks = Task.Run(() => { Parallel.ForEach(eventMessages, new ParallelOptions { MaxDegreeOfParallelism = MaxParallelism }, async(eventMessage) => { var activity = eventMessage.Activity; HeroCard card = null; switch (eventMessage.MessageType) { case MessageType.Event: card = CelebrationCard.GetEventCard(activity); break; case MessageType.Preview: card = CelebrationCard.GetPreviewCard(activity); break; } var task = this.connectorServiceHelper.SendPersonalMessageAsync(string.Empty, new List <Attachment> { card.ToAttachment() }, activity.ConversationId); RetryWithExponentialBackoff retryBackOff = new RetryWithExponentialBackoff(); await retryBackOff.RunAsync(task, eventMessage, this.SuccessCallback, this.FailureCallback); }); }); await tasks; }
// Handles event skip action private async Task HandleSkipEventAsync(IDialogContext context, IMessageActivity message) { var payload = ((JObject)message?.Value)?.ToObject <PreviewCardPayload>(); if (payload == null) { this.logProvider.LogInfo("Received message with no payload"); return; } // Get the event var celebrationEvent = await this.eventDataProvider.GetEventByIdAsync(payload.EventId, payload.OwnerAadObjectId); if (celebrationEvent == null) { this.logProvider.LogInfo($"Could not find event {payload.EventId} for user {payload.OwnerAadObjectId}"); await context.PostAsync(Strings.EventDoesNotExistMessage); return; } // Get the occurrence var occurrence = await this.eventDataProvider.GetEventOccurrenceByIdAsync(payload.OccurrenceId, payload.EventId); if (occurrence == null) { this.logProvider.LogInfo($"Could not find occurrence {payload.OccurrenceId} of event {payload.EventId} for user {payload.OwnerAadObjectId} (likely out of date card)"); await context.PostAsync(Strings.SkippedStaleEventMessage); return; } // Check that the occurrence is still in the future if (occurrence.DateTime < DateTimeOffset.UtcNow) { await context.PostAsync(Strings.EventPassedMessage); return; } // Mark the occurrence as skipped occurrence.Status = EventStatus.Skipped; await this.eventDataProvider.UpdateEventOccurrenceAsync(occurrence); // Update the card var updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetPreviewCard(celebrationEvent, payload.OccurrenceId, payload.OwnerName, false).ToAttachment()); await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); await context.PostAsync(string.Format(Strings.EventSkippedMessage, celebrationEvent.Title)); }
/// <summary> /// Handles event skip action. /// </summary> /// <param name="context">IDialogContext object.</param> /// <param name="activity">IAwaitable message activity.</param> /// <returns>Task.</returns> public async Task HandleEventSkipActions(IDialogContext context, IAwaitable <IMessageActivity> activity) { var message = (Activity)await activity; if (message.Value != null) { var replyMessage = string.Empty; var previewCardPayload = ((JObject)message.Value).ToObject <PreviewCardPayload>(); // Get event by eventId to check if it exist or not. CelebrationEvent celebrationEvent = await this.eventHelper.GetEventByEventIdAsync(previewCardPayload.EventId, previewCardPayload.OwnerAadObjectId); if (celebrationEvent != null) { if (previewCardPayload.UpcomingEventDate > DateTime.UtcNow.Date) { await this.eventHelper.UpdateRecurringEventAsync(previewCardPayload.EventId, EventStatus.Skipped); EventMessageActivity eventMessageActivity = new EventMessageActivity { Id = celebrationEvent.Id, OwnerName = previewCardPayload.OwnerName, ImageUrl = celebrationEvent.ImageURL, Message = celebrationEvent.Message, Title = celebrationEvent.Title, }; // Update the card IMessageActivity updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetPreviewCard(eventMessageActivity, false).ToAttachment()); updatedMessage.ReplyToId = message.ReplyToId; await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); replyMessage = string.Format(Strings.EventSkippedMessage, message.From.Name); } else { // event occurrence has already passed for current year. replyMessage = string.Format(Strings.EventPassedMessage); } } else { replyMessage = string.Format(Strings.EventNotExistMessage, previewCardPayload.Title); } await context.PostAsync(replyMessage); context.Done <object>(null); } }
/// <summary> /// Process request to send preview card /// </summary> /// <param name="currentDateTime">Current dateTime</param> /// <returns>A <see cref="Task"/>Representing the asynchronous operation</returns> public async Task <HttpResponseMessage> Post(string currentDateTime = "") { this.logProvider.LogInfo($"Processing events to send the reminder to owner. CurrentDateTime:{currentDateTime}"); DateTimeOffset currentDateTimeOffset; if (!DateTimeOffset.TryParse(currentDateTime, null, DateTimeStyles.AdjustToUniversal, out currentDateTimeOffset)) { currentDateTimeOffset = DateTimeOffset.UtcNow; } var events = await(await this.eventHelper.GetCelebrationEventsAsync(GetEventQuery(currentDateTimeOffset.Date))).ToListAsync(); this.logProvider.LogInfo($"found {events.Count} which are coming in next 72 hours."); if (events.Count > 0) { var existingRecurringEvents = await(await this.eventHelper.GetRecurringEventsAsync(events.Select(x => x.Id).ToList())).ToListAsync(); this.logProvider.LogInfo($"Found {existingRecurringEvents.Count} for which reminder has already sent"); int lastAttemptStatusCode = (int)HttpStatusCode.OK; string responseBody = string.Empty; // remove events which exist in Occurrences collection events.RemoveAll(x => existingRecurringEvents.Any(y => y.EventId == x.Id)); if (events.Count > 0) { this.users = await this.userManagementHelper.GetUsersByAadObjectIdsAsync(events.Select(x => x.OwnerAadObjectId).ToList()); this.connectorServiceHelper = new ConnectorServiceHelper(this.CreateConnectorClient(this.users.FirstOrDefault().ServiceUrl), this.logProvider); } // Loop each event and make entry in Occurrences collection to send preview and event card. foreach (var celebrationEvent in events) { this.logProvider.LogInfo("Processing event to send reminder", new Dictionary <string, string>() { { "EventId", celebrationEvent.Id }, { "UserObjectId", celebrationEvent.OwnerAadObjectId } }); // Get event owner information. var user = this.users.Where(x => x.AadObjectId == celebrationEvent.OwnerAadObjectId).FirstOrDefault(); // update conversation id if it is null. await this.ModifyUserDetailsAsync(user); DateTime upcomingEventDate = Common.GetUpcomingEventDate(celebrationEvent.Date, currentDateTimeOffset.Date); var timespan = Array.ConvertAll <string, int>(ApplicationSettings.TimeToPostCelebration.Split(':'), Convert.ToInt32); DateTime upcomingEventDateTime = upcomingEventDate.AddHours(timespan[0]).AddMinutes(timespan[1]); DateTimeOffset upcomingEventDateTimeInUTC = TimeZoneInfo.ConvertTimeToUtc(upcomingEventDateTime, TimeZoneInfo.FindSystemTimeZoneById(celebrationEvent.TimeZoneId)); // add an entry to Occurrence collection for all the upcoming event. EventOccurrence eventOccurrence = new EventOccurrence { EventId = celebrationEvent.Id, Date = upcomingEventDateTimeInUTC, }; await this.eventHelper.AddRecurringEventAsync(eventOccurrence); // Do not send reminder if event is today. if (upcomingEventDate != currentDateTimeOffset.Date) { // Add new entry to EventMessages collection for reminder. EventMessage eventMessage = new EventMessage { OccurrenceId = eventOccurrence.Id, EventId = celebrationEvent.Id, Activity = this.GetEventMessageActivity(celebrationEvent, user), MessageType = MessageType.Preview, ExpireAt = upcomingEventDate.AddHours(24), }; await this.eventHelper.AddEventMessageAsync(eventMessage); bool isMessageSentSuccessfully = false; Exception exception = null; try { HeroCard previewCard = CelebrationCard.GetPreviewCard(eventMessage.Activity); string message = string.Format(Strings.PreviewText, user.UserName); this.logProvider.LogInfo("Sending reminder message to the owner of the event", new Dictionary <string, string>() { { "EventId", celebrationEvent.Id }, { "Attachment", Newtonsoft.Json.JsonConvert.SerializeObject(previewCard) }, { "Message", message }, }); // Send reminder of event to owner. await this.connectorServiceHelper.SendPersonalMessageAsync( message, new List <Attachment> { previewCard.ToAttachment() }, user.ConversationId); this.logProvider.LogInfo($"Reminder message sent to the owner of the event. EventId: {celebrationEvent.Id}"); } catch (HttpException httpException) { lastAttemptStatusCode = httpException.GetHttpCode(); responseBody = httpException.GetHtmlErrorMessage(); exception = httpException; } catch (ErrorResponseException errorResponseException) { lastAttemptStatusCode = (int)errorResponseException.Response.StatusCode; responseBody = errorResponseException.Response.Content.ToString(); exception = errorResponseException; } catch (Exception ex) { lastAttemptStatusCode = (int)HttpStatusCode.BadRequest; responseBody = ex.ToString(); } finally { if (!isMessageSentSuccessfully) { this.logProvider.LogError("Failed to send reminder for upcoming event.", exception, new Dictionary <string, string> { { "EventId", eventMessage.EventId }, { "OccurrenceId", eventMessage.OccurrenceId }, { "eventActivity", eventMessage.Activity.ToString() }, { "LastAttemptStatusCode", lastAttemptStatusCode.ToString() }, { "LastAttemptTime", DateTime.UtcNow.ToString() }, { "ConversationId", user.ConversationId }, }); } MessageSendResult messageSendResult = new MessageSendResult() { LastAttemptTime = DateTime.UtcNow, StatusCode = lastAttemptStatusCode, ResponseBody = responseBody, }; await this.eventHelper.UpdateEventMessageAsync(eventMessage.Id, messageSendResult); } } else { this.logProvider.LogInfo("Not sending reminder for this event as its upcoming event date is today."); } } } return(new HttpResponseMessage(HttpStatusCode.OK)); }
// Send a messsage to the owner of the given event, reminding them that their event is coming up private async Task SendEventReminderAsync(CelebrationEvent celebrationEvent, DateTimeOffset currentDateTimeOffset) { this.logProvider.LogInfo($"Sending reminder for event {celebrationEvent.Id} (owner={celebrationEvent.OwnerAadObjectId}, date={celebrationEvent.Date.ToShortDateString()})"); // Determine the next occurrence of the event var deliveryTimeZone = TimeZoneInfo.FindSystemTimeZoneById(celebrationEvent.TimeZoneId); var currentTimeInDeliveryTimeZone = TimeZoneInfo.ConvertTimeFromUtc(currentDateTimeOffset.UtcDateTime, deliveryTimeZone); var upcomingEventDateTime = Common.GetNextOccurrenceAfterDateTime(celebrationEvent.Date.Add(this.timeToPostPreview), currentTimeInDeliveryTimeZone); var upcomingEventDateTimeInUTC = TimeZoneInfo.ConvertTimeToUtc(upcomingEventDateTime, deliveryTimeZone); // Do not send reminder if the next occurrence is not in the window var timeUntilNextOccurrence = upcomingEventDateTimeInUTC - currentDateTimeOffset; if ((timeUntilNextOccurrence.TotalMinutes < 0) || (upcomingEventDateTimeInUTC - currentDateTimeOffset) > TimeSpan.FromDays(this.daysInAdvanceToSendEventPreview)) { this.logProvider.LogInfo($"Next occurrence of event {celebrationEvent.Id} is not in the next {this.daysInAdvanceToSendEventPreview} days"); return; } // Add an entry to Occurrence collection for the event, so we know that we processed it var eventOccurrence = new EventOccurrence { EventId = celebrationEvent.Id, OwnerAadObjectId = celebrationEvent.OwnerAadObjectId, DateTime = upcomingEventDateTimeInUTC, }; eventOccurrence = await this.eventDataProvider.AddEventOccurrenceAsync(eventOccurrence); // Do not send reminder if we are within the period specified in the configuration if ((upcomingEventDateTimeInUTC - currentDateTimeOffset) < this.minimumTimeToProcessEvent) { this.logProvider.LogInfo($"Not sending reminder for event {celebrationEvent.Id} which is due in less than {(int)this.minimumTimeToProcessEvent.TotalHours} hours"); return; } // Get event owner information var user = await this.userManagementHelper.GetUserByAadObjectIdAsync(celebrationEvent.OwnerAadObjectId); await this.EnsureConversationWithUserAsync(user); // Send reminder of event to the owner var previewCard = CelebrationCard.GetPreviewCard(celebrationEvent, eventOccurrence.Id, user.DisplayName); var message = string.Format(Strings.EventPreviewMessageText, user.DisplayName); var activity = new Activity(ActivityTypes.Message) { Conversation = new ConversationAccount { Id = user.ConversationId }, Recipient = new ChannelAccount { Id = user.TeamsId }, Text = message, Summary = message, Attachments = new List <Attachment> { previewCard.ToAttachment() }, ServiceUrl = user.ServiceUrl, }; // Add new entry to EventMessages collection for message type "preview" to track the status of message sent var eventMessage = new EventMessage { EventId = celebrationEvent.Id, OccurrenceId = eventOccurrence.Id, Activity = activity, TenantId = user.TenantId, MessageType = MessageType.Preview, ExpireAt = upcomingEventDateTimeInUTC.AddHours(24), }; eventMessage = await this.eventDataProvider.AddEventMessageAsync(eventMessage); // Send the message try { await eventMessage.SendAsync(this.connectorClientFactory); this.logProvider.LogInfo($"Reminder message sent to the owner of event {celebrationEvent.Id}"); } catch (Exception ex) { this.logProvider.LogError($"Failed to send reminder for event {celebrationEvent.Id}", ex, new Dictionary <string, string> { { "EventId", eventMessage.EventId }, { "OccurrenceId", eventMessage.OccurrenceId }, { "ConversationId", user.ConversationId }, { "OccurrenceDateTime", eventOccurrence.DateTime.ToString() }, { "LastAttemptTime", DateTimeOffset.UtcNow.ToString() }, { "LastAttemptStatusCode", eventMessage.MessageSendResult?.StatusCode.ToString() }, { "ResponseBody", eventMessage.MessageSendResult?.ResponseBody }, }); throw; } finally { // Record the result of the send await this.eventDataProvider.UpdateEventMessageAsync(eventMessage); } }