public async Task EditPostReturnsCorrectViewAndViewModelWhenValidatorReturnsError() { var model = new TaskSummaryModel { EndDateTime = DateTimeOffset.Now.AddDays(-1), StartDateTime = DateTimeOffset.Now.AddDays(1) }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.Send(It.IsAny <EventByIdQuery>())).Returns(new Event { Campaign = new Campaign { TimeZoneId = "Eastern Standard Time" } }); var validator = new Mock <ITaskSummaryModelValidator>(); validator.Setup(x => x.Validate(It.IsAny <TaskSummaryModel>())).Returns(new List <KeyValuePair <string, string> > { new KeyValuePair <string, string>(nameof(TaskSummaryModel.EndDateTime), "Ending time cannot be earlier than the starting time") }); var sut = new TaskController(mediator.Object, validator.Object); var result = await sut.Edit(model) as ViewResult; var modelResult = result.ViewData.Model as TaskSummaryModel; Assert.IsType <ViewResult>(result); Assert.Equal(modelResult, model); }
public async Task EditPostReturnsHttpUnauthorizedResultWhenModelStateIsValidAndUserIsNotAnOrganizationAdminUser() { var startDateTime = DateTimeOffset.Now.AddDays(-1); var endDateTime = DateTimeOffset.Now.AddDays(1); var model = new TaskSummaryModel { StartDateTime = startDateTime, EndDateTime = endDateTime, OrganizationId = 1 }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.Send(It.IsAny <EventByIdQuery>())).Returns(new Event { Campaign = new Campaign { TimeZoneId = "Eastern Standard Time" }, StartDateTime = startDateTime.AddDays(-1), EndDateTime = endDateTime.AddDays(1) }); var validator = new Mock <ITaskSummaryModelValidator>(); validator.Setup(x => x.Validate(It.IsAny <TaskSummaryModel>())).Returns(new List <KeyValuePair <string, string> >()); var sut = new TaskController(mediator.Object, validator.Object); sut.SetDefaultHttpContext(); var result = await sut.Edit(model); Assert.IsType <UnauthorizedResult>(result); }
public async Task AssignRedirectsToRoute() { const int organizationId = 1; const int taskId = 1; var taskModelSummary = new TaskSummaryModel { EventId = 1 }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskModelSummary); mediator.Setup(x => x.Send(It.Is <EventByIdQuery>(y => y.EventId == taskModelSummary.EventId))).Returns(new Event { Campaign = new Campaign { ManagingOrganizationId = organizationId } }); var sut = new TaskController(mediator.Object); MakeUserOrganizationAdminUser(sut, organizationId.ToString()); var result = await sut.Assign(taskId, null) as RedirectToRouteResult; Assert.Equal(result.RouteValues["controller"], "Task"); Assert.Equal(result.RouteValues["Area"], "Admin"); Assert.Equal(result.RouteValues["action"], nameof(TaskController.Details)); Assert.Equal(result.RouteValues["id"], taskId); }
public void ReturnsNoErrorForNonItineraryTaskWhenModelsDatesAreValid() { var mockMediator = new Mock <IMediator>(); mockMediator.Setup(x => x.Send(It.IsAny <EventByIdQuery>())).Returns(new Event { Id = 1, Campaign = new Campaign { TimeZoneId = "UTC", }, StartDateTime = eventStartDate, EndDateTime = eventEndDate, EventType = EventTypes.EventManaged }); var validator = new TaskSummaryModelValidator(mockMediator.Object); var model = new TaskSummaryModel { StartDateTime = eventStartDate.AddDays(1), EndDateTime = eventEndDate.AddDays(-1) }; var errors = validator.Validate(model); Assert.True(errors.Count == 0); }
public async Task AssignSendsAssignTaskCommandAsync() { const int organizationId = 1; const int taskId = 1; var taskModelSummary = new TaskSummaryModel { EventId = 1 }; var userIds = new List <string> { "1", "2" }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskModelSummary); mediator.Setup(x => x.Send(It.Is <EventByIdQuery>(y => y.EventId == taskModelSummary.EventId))).Returns(new Event { Campaign = new Campaign { ManagingOrganizationId = organizationId } }); var sut = new TaskController(mediator.Object); MakeUserOrganizationAdminUser(sut, organizationId.ToString()); await sut.Assign(taskId, userIds); mediator.Verify(x => x.SendAsync(It.Is <AssignTaskCommandAsync>(y => y.TaskId == taskId && y.UserIds == userIds)), Times.Once); }
public async Task DeleteConfirmedRedirectsToCorrectAction() { const int organizationId = 1; const int taskId = 1; var taskSummaryModel = new TaskSummaryModel { OrganizationId = organizationId, EventId = 1 }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskSummaryModel); var sut = new TaskController(mediator.Object); MakeUserOrganizationAdminUser(sut, organizationId.ToString()); var result = await sut.DeleteConfirmed(taskId) as RedirectToActionResult; var routeValues = new Dictionary <string, object> { ["id"] = taskSummaryModel.EventId }; Assert.Equal(result.ActionName, nameof(EventController.Details)); Assert.Equal(result.ControllerName, "Event"); Assert.Equal(result.RouteValues, routeValues); }
public async Task EditPostSendsEditTaskCommandAsyncWhenModelStateIsValidAndUserIsOrganizationAdmin() { const int organizationId = 1; var taskSummaryModel = new TaskSummaryModel { OrganizationId = organizationId }; var startDateTime = DateTimeOffset.Now.AddDays(-1); var endDateTime = DateTimeOffset.Now.AddDays(1); var model = new TaskEditModel { StartDateTime = startDateTime, EndDateTime = endDateTime, OrganizationId = organizationId }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.Send(It.IsAny <EventByIdQuery>())).Returns(new Event { Campaign = new Campaign { TimeZoneId = "Eastern Standard Time" }, StartDateTime = startDateTime.AddDays(-1), EndDateTime = endDateTime.AddDays(1) }); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskSummaryModel); var sut = new TaskController(mediator.Object); MakeUserOrganizationAdminUser(sut, organizationId.ToString()); await sut.Edit(model); mediator.Verify(x => x.SendAsync(It.Is <EditTaskCommandAsync>(y => y.Task == model))); }
public List <KeyValuePair <string, string> > Validate(TaskSummaryModel model) { var result = new List <KeyValuePair <string, string> >(); if (model.StartDateTime.HasValue || model.EndDateTime.HasValue) { var campaignEvent = _mediator.Send(new EventByIdQuery { EventId = model.EventId }); var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(campaignEvent.Campaign.TimeZoneId); // sgordon: Date time conversion seems overly complex and may be refactored per #710 DateTimeOffset?convertedStartDateTime = null; if (model.StartDateTime.HasValue) { var startDateValue = model.StartDateTime.Value; var startDateTimeOffset = timeZoneInfo.GetUtcOffset(startDateValue); convertedStartDateTime = new DateTimeOffset(startDateValue.Year, startDateValue.Month, startDateValue.Day, startDateValue.Hour, startDateValue.Minute, 0, startDateTimeOffset); } DateTimeOffset?convertedEndDateTime = null; if (model.EndDateTime.HasValue) { var endDateValue = model.EndDateTime.Value; var endDateTimeOffset = timeZoneInfo.GetUtcOffset(endDateValue); convertedEndDateTime = new DateTimeOffset(endDateValue.Year, endDateValue.Month, endDateValue.Day, endDateValue.Hour, endDateValue.Minute, 0, endDateTimeOffset); } // Rule - End date cannot be earlier than start date if (convertedStartDateTime.HasValue && convertedEndDateTime.HasValue && convertedEndDateTime.Value < convertedStartDateTime.Value) { result.Add(new KeyValuePair <string, string>(nameof(model.EndDateTime), "End date cannot be earlier than the start date")); } // Rule - Start date cannot be out of range of parent event if (convertedStartDateTime.HasValue && convertedStartDateTime < campaignEvent.StartDateTime) { result.Add(new KeyValuePair <string, string>(nameof(model.StartDateTime), "Start date cannot be earlier than the event start date " + campaignEvent.StartDateTime.ToString("d"))); } // Rule - End date cannot be out of range of parent event if (convertedEndDateTime.HasValue && convertedEndDateTime > campaignEvent.EndDateTime) { result.Add(new KeyValuePair <string, string>(nameof(model.EndDateTime), "End date cannot be later than the event end date " + campaignEvent.EndDateTime.ToString("d"))); } // Rule - Itinerary tasks must start and end on same calendar day if (campaignEvent.EventType == EventType.Itinerary && convertedStartDateTime.HasValue && convertedEndDateTime.HasValue) { if (convertedStartDateTime.Value.Date != convertedEndDateTime.Value.Date) { result.Add(new KeyValuePair <string, string>(nameof(model.EndDateTime), "For itinerary events the task end date must occur on the same day as the start date. Tasks cannot span multiple days")); } } } return(result); }
public void ValidationShouldFail_IfNoNameIsSupplied() { var model = new TaskSummaryModel(); var result = ValidateModel(model); var nameError = result.Where(r => r.ErrorMessage == "The Name field is required.").FirstOrDefault(); Assert.NotNull(nameError); }
public List<KeyValuePair<string, string>> Validate(TaskSummaryModel model) { var result = new List<KeyValuePair<string, string>>(); if (model.StartDateTime.HasValue || model.EndDateTime.HasValue) { var campaignEvent = _mediator.Send(new EventByIdQuery { EventId = model.EventId }); var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(campaignEvent.Campaign.TimeZoneId); // sgordon: Date time conversion seems overly complex and may be refactored per #710 DateTimeOffset? convertedStartDateTime = null; if (model.StartDateTime.HasValue) { var startDateValue = model.StartDateTime.Value; var startDateTimeOffset = timeZoneInfo.GetUtcOffset(startDateValue); convertedStartDateTime = new DateTimeOffset(startDateValue.Year, startDateValue.Month, startDateValue.Day, startDateValue.Hour, startDateValue.Minute, 0, startDateTimeOffset); } DateTimeOffset? convertedEndDateTime = null; if (model.EndDateTime.HasValue) { var endDateValue = model.EndDateTime.Value; var endDateTimeOffset = timeZoneInfo.GetUtcOffset(endDateValue); convertedEndDateTime = new DateTimeOffset(endDateValue.Year, endDateValue.Month, endDateValue.Day, endDateValue.Hour, endDateValue.Minute, 0, endDateTimeOffset); } // Rule - End date cannot be earlier than start date if (convertedStartDateTime.HasValue && convertedEndDateTime.HasValue && convertedEndDateTime.Value < convertedStartDateTime.Value) { result.Add(new KeyValuePair<string, string>(nameof(model.EndDateTime), "End date cannot be earlier than the start date")); } // Rule - Start date cannot be out of range of parent event if (convertedStartDateTime.HasValue && convertedStartDateTime < campaignEvent.StartDateTime) { result.Add(new KeyValuePair<string, string>(nameof(model.StartDateTime), "Start date cannot be earlier than the event start date " + campaignEvent.StartDateTime.ToString("d"))); } // Rule - End date cannot be out of range of parent event if (convertedEndDateTime.HasValue && convertedEndDateTime > campaignEvent.EndDateTime) { result.Add(new KeyValuePair<string, string>(nameof(model.EndDateTime), "End date cannot be later than the event end date " + campaignEvent.EndDateTime.ToString("d"))); } // Rule - Itinerary tasks must start and end on same calendar day if (campaignEvent.EventType == EventType.Itinerary && convertedStartDateTime.HasValue && convertedEndDateTime.HasValue) { if (convertedStartDateTime.Value.Date != convertedEndDateTime.Value.Date) { result.Add(new KeyValuePair<string, string>(nameof(model.EndDateTime), "For itinerary events the task end date must occur on the same day as the start date. Tasks cannot span multiple days")); } } } return result; }
public async Task Handle(UserUnenrolls notification) { // don't let problem with notification keep us from continuing try { var notificationModel = await _mediator.SendAsync(new EventDetailForNotificationQueryAsync { EventId = notification.EventId, UserId = notification.UserId }) .ConfigureAwait(false); var campaignContact = notificationModel.CampaignContacts.SingleOrDefault(tc => tc.ContactType == (int)ContactTypes.Primary); var adminEmail = campaignContact?.Contact.Email; if (string.IsNullOrWhiteSpace(adminEmail)) { return; } TaskSummaryModel taskModel = null; string remainingRequiredVolunteersPhrase; taskModel = notificationModel.Tasks.FirstOrDefault(t => t.Id == notification.TaskIds[0]) ?? new TaskSummaryModel(); remainingRequiredVolunteersPhrase = $"{taskModel.NumberOfVolunteersSignedUp}/{taskModel.NumberOfVolunteersRequired}"; var eventLink = $"View event: {_options.Value.SiteBaseUrl}Admin/Event/Details/{notificationModel.EventId}"; var subject = $"A volunteer has unenrolled from a task"; var message = new StringBuilder(); message.AppendLine($"A volunteer has unenrolled from a task."); message.AppendLine($" Volunteer: {notificationModel.Volunteer.Name} ({notificationModel.Volunteer.Email})"); message.AppendLine($" Campaign: {notificationModel.CampaignName}"); message.AppendLine($" Event: {notificationModel.EventName} ({eventLink})"); message.AppendLine($" Task: {taskModel.Name} ({$"View task: {_options.Value.SiteBaseUrl}Admin/Task/Details/{taskModel.Id}"})"); message.AppendLine($" Task Start Date: {taskModel.StartDateTime?.Date.ToShortDateString()}"); message.AppendLine($" Remaining/Required Volunteers: {remainingRequiredVolunteersPhrase}"); message.AppendLine(); var command = new NotifyVolunteersCommand { ViewModel = new NotifyVolunteersViewModel { EmailMessage = message.ToString(), HtmlMessage = message.ToString(), EmailRecipients = new List <string> { campaignContact.Contact.Email }, Subject = subject } }; await _mediator.SendAsync(command).ConfigureAwait(false); } catch (Exception e) { _logger.LogError($"Exception encountered: message={e.Message}, innerException={e.InnerException}, stacktrace={e.StackTrace}"); } }
public void ReturnsNoErrorForItineraryTaskWhenModelsDatesAreValid() { var validator = GetValidator(); var model = new TaskSummaryModel { StartDateTime = eventStartDate.AddDays(1), EndDateTime = eventStartDate.AddDays(1).AddHours(2), }; var errors = validator.Validate(model); Assert.True(errors.Count == 0); }
public void ReturnsCorrectErrorWhenItineraryTaskWithStartAndEndDatesNotOnSameDay() { var validator = GetValidator(); var model = new TaskSummaryModel { StartDateTime = new DateTimeOffset(new DateTime(1999, 12, 1)), EndDateTime = new DateTimeOffset(new DateTime(2000, 12, 1)) }; var errors = validator.Validate(model); Assert.True(errors.Exists(x => x.Key.Equals("EndDateTime"))); Assert.Equal(errors.Find(x => x.Key == "EndDateTime").Value, "For itinerary events the task end date must occur on the same day as the start date. Tasks cannot span multiple days"); }
public void ReturnsCorrectErrorWhenModelsEndDateTimeIsGreaterThanParentEventStartDate() { var validator = GetValidator(); var model = new TaskSummaryModel { StartDateTime = eventStartDate.AddDays(1), EndDateTime = eventEndDate.AddDays(10) }; var errors = validator.Validate(model); Assert.True(errors.Exists(x => x.Key.Equals("EndDateTime"))); Assert.Equal(errors.Find(x => x.Key == "EndDateTime").Value, "End date cannot be later than the event end date " + eventEndDate.ToString("d")); }
public async Task DetailsReturnsCorrectViewModelAndView() { var taskSummaryModel = new TaskSummaryModel(); var mediator = new Mock <IMediator>(); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskSummaryModel); var sut = new TaskController(mediator.Object); var result = await sut.Details(It.IsAny <int>()) as ViewResult; var modelResult = result.ViewData.Model as TaskSummaryModel; Assert.IsType <ViewResult>(result); Assert.IsType <TaskSummaryModel>(modelResult); Assert.Equal(modelResult, taskSummaryModel); }
public async Task CreatePostRedirectsToCorrectAction() { const int organizationId = 1; var taskSummaryModel = new TaskSummaryModel { OrganizationId = organizationId }; var startDateTime = DateTimeOffset.Now.AddDays(-1); var endDateTime = DateTimeOffset.Now.AddDays(1); const int TaskSummaryModelId = 1; var model = new TaskSummaryModel { Id = TaskSummaryModelId, StartDateTime = startDateTime, EndDateTime = endDateTime, OrganizationId = organizationId }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.Send(It.IsAny <EventByIdQuery>())).Returns(new Event { Campaign = new Campaign { TimeZoneId = "Eastern Standard Time" }, StartDateTime = startDateTime.AddDays(-1), EndDateTime = endDateTime.AddDays(1) }); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskSummaryModel); var routeValues = new Dictionary <string, object> { ["id"] = model.Id }; var validator = new Mock <ITaskSummaryModelValidator>(); validator.Setup(x => x.Validate(It.IsAny <TaskSummaryModel>())).Returns(new List <KeyValuePair <string, string> >()); var sut = new TaskController(mediator.Object, validator.Object); MakeUserOrganizationAdminUser(sut, organizationId.ToString()); var result = await sut.Create(TaskSummaryModelId, model) as RedirectToActionResult; Assert.Equal(result.ActionName, nameof(TaskController.Details)); Assert.Equal(result.ControllerName, "Event"); Assert.Equal(result.RouteValues, routeValues); }
public async Task <IActionResult> Edit(TaskSummaryModel model) { var errors = _taskDetailModelValidator.Validate(model); errors.ToList().ForEach(e => ModelState.AddModelError(e.Key, e.Value)); if (ModelState.IsValid) { if (!User.IsOrganizationAdmin(model.OrganizationId)) { return(HttpUnauthorized()); } await _mediator.SendAsync(new EditTaskCommandAsync { Task = model }); return(RedirectToAction(nameof(Details), "Task", new { id = model.Id })); } return(View(model)); }
public ActionResult TasksSummary() { var runPath = GetRunPath(); //var taskName = GetTaskName(); if (!string.IsNullOrEmpty(runPath)) { StringBuilder summaryBuilder = new StringBuilder(); var logTxt = FileClient.ReadAllText(Path.Combine(runPath, WebConfigurationManager.AppSettings[FILE_WONKA_LOG])); if (!string.IsNullOrEmpty(logTxt)) { summaryBuilder.AppendFormat("Wonka Log {0} {1}", Environment.NewLine, logTxt); } var processingTxt = FileClient.ReadAllText(Path.Combine(runPath, WebConfigurationManager.AppSettings[PROCESSING_DATA_FILE])); if (!string.IsNullOrEmpty(processingTxt)) { summaryBuilder.AppendFormat("Processing Log {0} {1}", Environment.NewLine, processingTxt); } var errorTxt = FileClient.ReadAllText(Path.Combine(runPath, WebConfigurationManager.AppSettings[ERROR_DATA_FILE])); if (!string.IsNullOrEmpty(processingTxt)) { summaryBuilder.AppendFormat("Error Log {0} {1}", Environment.NewLine, errorTxt); } var model = new TaskSummaryModel() { BuildLabel = "build x.x.x.", BuildTime = "2 minutes", BuildLog = summaryBuilder.ToString(), }; return(PartialView("_TasksSummary", model)); } return(PartialView("_TasksSummary")); }
public async Task <IActionResult> Create(int eventId, TaskSummaryModel model) { var errors = _taskDetailModelValidator.Validate(model); errors.ToList().ForEach(e => ModelState.AddModelError(e.Key, e.Value)); if (ModelState.IsValid) { if (!User.IsOrganizationAdmin(model.OrganizationId)) { return(HttpUnauthorized()); } await _mediator.SendAsync(new EditTaskCommandAsync { Task = model }); //mgmccarthy: I'm assuming this is EventController in the Admin area return(RedirectToAction(nameof(Details), "Event", new { id = eventId })); } return(View("Edit", model)); }
public async Task AssignReturnsHttpUnauthorizedResultWhenUserIsNotOrgAdmin() { var taskModelSummary = new TaskSummaryModel { ActivityId = 1 }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskModelSummary); mediator.Setup(x => x.Send(It.IsAny <ActivityByActivityIdQuery>())).Returns(new Activity { Campaign = new Campaign { ManagingOrganizationId = 1 } }); var sut = new TaskController(mediator.Object); sut.SetDefaultHttpContext(); var result = await sut.Assign(1, null); Assert.IsType <HttpUnauthorizedResult>(result); }
public IActionResult Create(int eventId) { var campaignEvent = GetEventBy(eventId); if (campaignEvent == null || !User.IsOrganizationAdmin(campaignEvent.Campaign.ManagingOrganizationId)) { return(HttpUnauthorized()); } var viewModel = new TaskSummaryModel { EventId = campaignEvent.Id, EventName = campaignEvent.Name, CampaignId = campaignEvent.CampaignId, CampaignName = campaignEvent.Campaign.Name, OrganizationId = campaignEvent.Campaign.ManagingOrganizationId, TimeZoneId = campaignEvent.Campaign.TimeZoneId, StartDateTime = campaignEvent.StartDateTime, EndDateTime = campaignEvent.EndDateTime }; return(View("Edit", viewModel)); }
public void ReturnsCorrectErrorWhenEndDateTimeIsLessThanStartDateTime() { var mockMediator = new Mock <IMediator>(); mockMediator.Setup(x => x.Send(It.IsAny <EventByIdQuery>())).Returns(new Event { Id = 1, Campaign = new Campaign { TimeZoneId = "UTC" } }); var validator = new TaskSummaryModelValidator(mockMediator.Object); var model = new TaskSummaryModel { StartDateTime = new DateTimeOffset(new DateTime(2000, 1, 1)), EndDateTime = new DateTimeOffset(new DateTime(1999, 1, 1)) }; var errors = validator.Validate(model); Assert.True(errors.Exists(x => x.Key.Equals("EndDateTime"))); Assert.Equal(errors.Find(x => x.Key == "EndDateTime").Value, "End date cannot be earlier than the start date"); }
public async Task AssignSendsTaskQueryAsyncWithCorrectTaskId() { const int taskId = 1; var taskModelSummary = new TaskSummaryModel { ActivityId = 1 }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.Send(It.IsAny <ActivityByActivityIdQuery>())).Returns(new Activity { Campaign = new Campaign { ManagingOrganizationId = 1 } }); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskModelSummary); var sut = new TaskController(mediator.Object); sut.SetDefaultHttpContext(); await sut.Assign(taskId, null); mediator.Verify(x => x.SendAsync(It.Is <TaskQueryAsync>(y => y.TaskId == taskId)), Times.Once); }
public async Task DeleteReturnsCorrectViewModelAndView() { const int organizationId = 1; var taskSummaryModel = new TaskSummaryModel { OrganizationId = organizationId }; var mediator = new Mock <IMediator>(); mediator.Setup(x => x.SendAsync(It.IsAny <TaskQueryAsync>())).ReturnsAsync(taskSummaryModel); var sut = new TaskController(mediator.Object); MakeUserOrganizationAdminUser(sut, organizationId.ToString()); var result = await sut.Delete(It.IsAny <int>()) as ViewResult; var modelResult = result.ViewData.Model as TaskSummaryModel; Assert.IsType <ViewResult>(result); Assert.IsType <TaskSummaryModel>(modelResult); Assert.Equal(modelResult, taskSummaryModel); }
public async Task Handle(UserUnenrolls notification) { // don't let problem with notification keep us from continuing try { var notificationModel = _mediator.Send(new ActivityDetailForNotificationQuery { ActivityId = notification.ActivityId, UserId = notification.UserId }); var campaignContact = notificationModel.CampaignContacts.SingleOrDefault(tc => tc.ContactType == (int)ContactTypes.Primary); var adminEmail = campaignContact?.Contact.Email; if (string.IsNullOrWhiteSpace(adminEmail)) { return; } TaskSummaryModel taskModel = null; string remainingRequiredVolunteersPhrase; var isActivityUnenroll = (notificationModel.ActivityType == ActivityTypes.ActivityManaged); if (isActivityUnenroll) { remainingRequiredVolunteersPhrase = $"{notificationModel.UsersSignedUp.Count}/{notificationModel.NumberOfVolunteersRequired}"; } else { taskModel = notificationModel.Tasks.FirstOrDefault(t => t.Id == notification.TaskIds[0]) ?? new TaskSummaryModel(); remainingRequiredVolunteersPhrase = $"{taskModel.NumberOfVolunteersSignedUp}/{taskModel.NumberOfVolunteersRequired}"; } var activityLink = $"View activity: {_options.Value.SiteBaseUrl}Admin/Activity/Details/{notificationModel.ActivityId}"; var subject = $"A volunteer has unenrolled from {(isActivityUnenroll ? "an activity" : "a task")}"; var message = new StringBuilder(); message.AppendLine($"A volunteer has unenrolled from {(isActivityUnenroll ? "an activity" : "a task")}."); message.AppendLine($" Volunteer: {notificationModel.Volunteer.Name} ({notificationModel.Volunteer.Email})"); message.AppendLine($" Campaign: {notificationModel.CampaignName}"); message.AppendLine($" Activity: {notificationModel.ActivityName} ({activityLink})"); if (!isActivityUnenroll) { message.AppendLine($" Task: {taskModel.Name} ({$"View task: {_options.Value.SiteBaseUrl}Admin/Task/Details/{taskModel.Id}"})"); message.AppendLine($" Task Start Date: {taskModel.StartDateTime?.Date.ToShortDateString()}"); } message.AppendLine($" Remaining/Required Volunteers: {remainingRequiredVolunteersPhrase}"); message.AppendLine(); if (isActivityUnenroll) { var assignedTasks = notificationModel.Tasks.Where(t => t.AssignedVolunteers.Any(au => au.UserId == notificationModel.Volunteer.Id)).ToList(); if (assignedTasks.Count == 0) { message.AppendLine("This volunteer had not been assigned to any tasks."); } else { message.AppendLine("This volunteer had been assigned to the following tasks:"); message.AppendLine(" Name Description Start Date TaskLink"); foreach (var assignedTask in assignedTasks) { var taskLink = $"View task: {_options.Value.SiteBaseUrl}Admin/Task/Details/{assignedTask.Id}"; message.AppendFormat(" {0}{1}{2:d}{3}", assignedTask.Name?.Substring(0, Math.Min(15, assignedTask.Name.Length)).PadRight(17, ' ') ?? "None".PadRight(17, ' '), assignedTask.Description?.Substring(0, Math.Min(25, assignedTask.Description.Length)).PadRight(26, ' ') ?? "None".PadRight(26, ' '), assignedTask.StartDateTime?.Date.ToShortDateString().PadRight(21, ' ') ?? "".PadRight(21, ' '), taskLink); } } } var command = new NotifyVolunteersCommand { ViewModel = new NotifyVolunteersViewModel { EmailMessage = message.ToString(), HtmlMessage = message.ToString(), EmailRecipients = new List <string> { campaignContact.Contact.Email }, Subject = subject } }; await _mediator.SendAsync(command).ConfigureAwait(false); } catch (Exception e) { _logger.LogError($"Exception encountered: message={e.Message}, innerException={e.InnerException}, stacktrace={e.StackTrace}"); } }