public async void CallLogicApp(IReadOnlyList <Document> events) { try { // Have the HttpClient factory create a new client instance. var httpClient = _httpClientFactory.CreateClient("LogicAppClient"); // Create the payload to send to the Logic App. foreach (var e in events) { var payload = new LogicAppAlert { data = JsonConvert.SerializeObject(e), recipientEmail = Environment.GetEnvironmentVariable("RecipientEmail") }; var postBody = JsonConvert.SerializeObject(payload); var httpResult = await httpClient.PostAsync(Environment.GetEnvironmentVariable("LogicAppUrl"), new StringContent(postBody, Encoding.UTF8, "application/json")); } } catch (Exception ex) { log.LogError(ex.Message); } }
/// <summary> /// Adds alert messages to an Azure Storage queue. This allows alerts to be sent as a summary /// of events within a given period of time, as defined in the <see cref="Settings"/>. /// </summary> /// <param name="alert">The alert to queue.</param> /// <returns></returns> protected async Task QueueTripAlert(LogicAppAlert alert) { // Use the QueueResolver to retrieve a reference to the Azure Storage queue. var alertsQueue = _queueResolver.GetQueue(WellKnown.StorageQueues.AlertQueueName); // Create a message and add it to the queue. var message = new CloudQueueMessage(JsonConvert.SerializeObject(alert)); await alertsQueue.AddMessageAsync(message); }
/// <summary> /// Uses the Logic App's HTTP trigger to send the alert. /// </summary> /// <param name="alert"></param> /// <returns></returns> protected async Task SendAlertToLogicApp(LogicAppAlert alert) { // Have the HttpClient factory create a new client instance. var httpClient = _httpClientFactory.CreateClient(NamedHttpClients.LogicAppClient); var postBody = JsonConvert.SerializeObject(alert); // Post the alert to the Logic App. await httpClient.PostAsync(Environment.GetEnvironmentVariable("LogicAppUrl"), new StringContent(postBody, Encoding.UTF8, "application/json")); }
/// <summary> /// Triggers the Logic App through the LogicAppUrl environment variable/app setting /// by instantiating a new <see cref="HttpClient"/> instance from the /// <see cref="HttpClientFactory"/> and POSTing payload data, including the /// <see cref="Trip"/> information, <see cref="averageRefrigerationUnitTemp"/> /// aggregate value, and the RecipientEmail environment variable. /// </summary> /// <param name="averageRefrigerationUnitTemp">Average refrigeration unit temperature /// reading in the telemetry batch.</param> /// <returns></returns> public async Task SendTripAlert(double averageRefrigerationUnitTemp) { var settings = await GetSettingsFromDatabase(); // If the user has indicated that they want alerts and they have specified a recipient email address, send the alert as needed. if (settings != null && !string.IsNullOrWhiteSpace(settings.recipientEmailAddress) && settings.SendAlerts) { // Only send or queue the alert if the trip status type is enabled in the alert settings. if (settings.sendTripStartedAlerts && _trip.status == WellKnown.Status.Active || settings.sendTripCompletedAlerts && _trip.status == WellKnown.Status.Completed || settings.sendTripDelayedAlerts && _trip.status == WellKnown.Status.Delayed) { // Create the payload to send to the Logic App. var payload = new LogicAppAlert { consignmentId = _trip.consignmentId, customer = _trip.consignment.customer, deliveryDueDate = _trip.consignment.deliveryDueDate, hasHighValuePackages = _trip.packages.Any(p => p.highValue), id = _trip.id, lastRefrigerationUnitTemperatureReading = averageRefrigerationUnitTemp, location = _trip.location, lowestPackageStorageTemperature = _trip.packages.Min(p => p.storageTemperature), odometerBegin = _trip.odometerBegin, odometerEnd = _trip.odometerEnd, plannedTripDistance = _trip.plannedTripDistance, tripStarted = _trip.tripStarted, tripEnded = _trip.tripEnded, status = _trip.status, vin = _trip.vin, temperatureSetting = _trip.temperatureSetting, recipientEmail = settings.recipientEmailAddress }; if (string.IsNullOrWhiteSpace(settings.sendAlertInterval)) { // The send alert interval setting is null or empty, indicating that every alert should be sent individually. payload.isSummary = false; // Send the alert to the Logic App. await SendAlertToLogicApp(payload); } else { // The settings specify that an alert summary should be sent on a schedule. Add the alert to a queue. await QueueTripAlert(payload); } } } }
public async Task TripProcessor([CosmosDBTrigger( databaseName: WellKnown.COSMOSDB_DB_NAME, collectionName: WellKnown.COSMOSDB_COLLECTION_NAME_TELEMETRY, ConnectionStringSetting = WellKnown.COSMOSDB_CONNECTIONSTRING_NAME, LeaseCollectionName = WellKnown.COSMOSDB_COLLECTION_NAME_LEASES, LeaseCollectionPrefix = WellKnown.COSMOSDB_LEASE_PREFIX_TRIPS, LeasesCollectionThroughput = LEASES_COLLECTION_THROUGHPUT, CreateLeaseCollectionIfNotExists = true, StartFromBeginning = true)] IReadOnlyList <Document> vehicleEvents, ILogger log) { log.LogInformation($"Evaluating {vehicleEvents.Count} events from Cosmos DB to optionally update Trip and Consignment metadata."); // Retrieve the Trip records by VIN, compare the odometer reading to the starting odometer reading to calculate miles driven, // and update the Trip and Consignment status and send an alert if needed once completed. var sendTripAlert = false; var database = WellKnown.COSMOSDB_DB_NAME; var metadataContainer = WellKnown.COSMOSDB_COLLECTION_NAME_METADATA; if (vehicleEvents.Count > 0) { foreach (var group in vehicleEvents.GroupBy(singleEvent => singleEvent.GetPropertyValue <string>(nameof(VehicleEvent.vin)))) { // TODO 3: We have grouped the events by vehicle VIN. Assign local variables to hold the VIN, get the max odometer value, and average refrigeration unit temperature. // Complete: var vin = ...; var odometerHigh = ...; var averageRefrigerationUnitTemp = ...; // First, retrieve the metadata Cosmos DB container reference: var container = _cosmosClient.GetContainer(database, metadataContainer); // Create a query, defining the partition key so we don't execute a fan-out query (saving RUs), where the entity type is a Trip and the status is not Completed, Canceled, or Inactive. var query = container.GetItemLinqQueryable <Trip>(requestOptions: new QueryRequestOptions { PartitionKey = new Microsoft.Azure.Cosmos.PartitionKey(vin) }) .Where(p => p.status != WellKnown.Status.Completed && p.status != WellKnown.Status.Canceled && p.status != WellKnown.Status.Inactive && p.entityType == WellKnown.EntityTypes.Trip) .ToFeedIterator(); if (query.HasMoreResults) { // Only retrieve the first result. var trip = (await query.ReadNextAsync()).FirstOrDefault(); if (trip != null) { // Retrieve the Consignment record. var document = await container.ReadItemAsync <Consignment>(trip.consignmentId, new Microsoft.Azure.Cosmos.PartitionKey(trip.consignmentId)); var consignment = document.Resource; var updateTrip = false; var updateConsignment = false; // Calculate how far along the vehicle is for this trip. var milesDriven = odometerHigh - trip.odometerBegin; if (milesDriven >= trip.plannedTripDistance) { // The trip is completed! trip.status = WellKnown.Status.Completed; trip.odometerEnd = odometerHigh; trip.tripEnded = DateTime.UtcNow; consignment.status = WellKnown.Status.Completed; // Update the trip and consignment records. updateTrip = true; updateConsignment = true; sendTripAlert = true; } else { if (DateTime.UtcNow >= consignment.deliveryDueDate && trip.status != WellKnown.Status.Delayed) { // The trip is delayed! trip.status = WellKnown.Status.Delayed; consignment.status = WellKnown.Status.Delayed; // Update the trip and consignment records. updateTrip = true; updateConsignment = true; sendTripAlert = true; } } if (trip.tripStarted == null) { // Set the trip start date. trip.tripStarted = DateTime.UtcNow; // Set the trip and consignment status to Active. trip.status = WellKnown.Status.Active; consignment.status = WellKnown.Status.Active; updateTrip = true; updateConsignment = true; sendTripAlert = true; } // Update the trip and consignment records. // TODO 4: Complete the code to update the Trip and Consignment records. // Complete: if (updateTrip) { ... } if (updateConsignment) { ... } // Send a trip alert. if (sendTripAlert) { // Have the HttpClient factory create a new client instance. var httpClient = _httpClientFactory.CreateClient(WellKnown.LOGIC_APP_CLIENT); // Create the payload to send to the Logic App. var payload = new LogicAppAlert { consignmentId = trip.consignmentId, customer = trip.consignment.customer, deliveryDueDate = trip.consignment.deliveryDueDate, hasHighValuePackages = trip.packages.Any(p => p.highValue), id = trip.id, lastRefrigerationUnitTemperatureReading = averageRefrigerationUnitTemp, location = trip.location, lowestPackageStorageTemperature = trip.packages.Min(p => p.storageTemperature), odometerBegin = trip.odometerBegin, odometerEnd = trip.odometerEnd, plannedTripDistance = trip.plannedTripDistance, tripStarted = trip.tripStarted, tripEnded = trip.tripEnded, status = trip.status, vin = trip.vin, temperatureSetting = trip.temperatureSetting, recipientEmail = Environment.GetEnvironmentVariable(WellKnown.RECIPIENT_EMAIL_NAME) }; var postBody = JsonConvert.SerializeObject(payload); // TODO 5: Add code to post the LogicAppAlert payload to the Logic App in JSON format. // Complete: await httpClient.PostAsync( ... )); } } } } } }
public async Task SendAlertSummary() { var compareDate = DateTime.UtcNow; var settings = await GetSettingsFromDatabase(); // If the user has indicated that they want alerts and they have specified a recipient email address, and // they have specified a summary alert interval (sendAlertInterval is not empty), then retrieve the // AlertSummaryHistory document from the database to determine if it is time to send a new alert summary. if (settings != null && !string.IsNullOrWhiteSpace(settings.recipientEmailAddress) && settings.SendAlerts && !string.IsNullOrWhiteSpace(settings.sendAlertInterval)) { var alertSummaryHistory = new AlertSummaryHistory(); // Retrieve the alert summary history document from the alerts container. var response = await _alertsContainer.ReadItemAsync <AlertSummaryHistory>(WellKnown.EntityTypes.AlertSummaryHistory, new PartitionKey(WellKnown.EntityTypes.AlertSummaryHistory)); if (response.StatusCode == HttpStatusCode.OK) { alertSummaryHistory = response.Resource; } // Retrieve the alert summary schedule from the defined cron notation. var schedule = CrontabSchedule.Parse(settings.sendAlertInterval); // Find the next schedule send date. var nextOccurrence = schedule.GetNextOccurrence(alertSummaryHistory.summaryAlertLastSent ?? compareDate.AddDays(-1)); if (nextOccurrence <= compareDate) { // It is time to send a new alert summary. _log.LogInformation($"Sending an alert summary based on next scheduled send date of {nextOccurrence}"); // Use the QueueResolver to retrieve a reference to the Azure Storage queue. var alertsQueue = _queueResolver.GetQueue(WellKnown.StorageQueues.AlertQueueName); // Fetch the queue attributes. alertsQueue.FetchAttributes(); // Retrieve the cached approximate message count. var cachedMessageCount = alertsQueue.ApproximateMessageCount; _log.LogInformation($"Number of alert messages in the queue: {cachedMessageCount}"); if (cachedMessageCount.HasValue && cachedMessageCount.Value > 0) { // Set the batch size for number of messages to retrieve from the queue (max: 32). var batchSize = 32; // Determine how many loops we need to retrieve the messages, based on the batch size and number of messages. var loops = (int)Math.Ceiling((double)cachedMessageCount.Value / batchSize); var alerts = new List <LogicAppAlert>(); // Loop through the batch size of messages until they are all retrieved. for (var loop = 0; loop < loops; loop++) { foreach (var message in await alertsQueue.GetMessagesAsync(batchSize)) { var alert = JsonConvert.DeserializeObject <LogicAppAlert>(message.AsString); alerts.Add(alert); // Delete the message from the queue. await alertsQueue.DeleteMessageAsync(message); } } var delayed = alerts.Where(a => a.status == WellKnown.Status.Delayed).ToList(); var payload = new LogicAppAlert { tripsStarted = alerts.Count(a => a.status == WellKnown.Status.Active), tripsCompleted = alerts.Count(a => a.status == WellKnown.Status.Completed), tripsDelayed = delayed.Count, delayedVINs = delayed.Count > 0 ? string.Join(", ", delayed.Select(d => d.vin)) : "None", recipientEmail = settings.recipientEmailAddress, isSummary = true }; // Send the summarized alert to the Logic App via its HTTP trigger. await SendAlertToLogicApp(payload); // Upsert (insert or update) the alert summary history to keep track of when we sent the alert. alertSummaryHistory.summaryAlertLastSent = compareDate; await _alertsContainer.UpsertItemAsync(alertSummaryHistory, new PartitionKey(alertSummaryHistory.id)); } } } }
public async Task TripProcessor([CosmosDBTrigger( databaseName: "ContosoAuto", collectionName: "telemetry", ConnectionStringSetting = "CosmosDBConnection", LeaseCollectionName = "leases", LeaseCollectionPrefix = "trips", CreateLeaseCollectionIfNotExists = true, StartFromBeginning = true)] IReadOnlyList <Document> vehicleEvents, ILogger log) { log.LogInformation($"Evaluating {vehicleEvents.Count} events from Cosmos DB to optionally update Trip and Consignment metadata."); // Retrieve the Trip records by VIN, compare the odometer reading to the starting odometer reading to calculate miles driven, // and update the Trip and Consignment status and send an alert if needed once completed. var sendTripAlert = false; var database = "ContosoAuto"; var metadataContainer = "metadata"; if (vehicleEvents.Count > 0) { foreach (var group in vehicleEvents.GroupBy(singleEvent => singleEvent.GetPropertyValue <string>("vin"))) { var vin = group.Key; var odometerHigh = group.Max(item => item.GetPropertyValue <double>("odometer")); var averageRefrigerationUnitTemp = group.Average(item => item.GetPropertyValue <double>("refrigerationUnitTemp")); // First, retrieve the metadata Cosmos DB container reference: var container = _cosmosClient.GetContainer(database, metadataContainer); // Create a query, defining the partition key so we don't execute a fan-out query (saving RUs), where the entity type is a Trip and the status is not Completed, Canceled, or Inactive. var query = container.GetItemLinqQueryable <Trip>(requestOptions: new QueryRequestOptions { PartitionKey = new Microsoft.Azure.Cosmos.PartitionKey(vin) }) .Where(p => p.status != WellKnown.Status.Completed && p.status != WellKnown.Status.Canceled && p.status != WellKnown.Status.Inactive && p.entityType == WellKnown.EntityTypes.Trip) .ToFeedIterator(); if (query.HasMoreResults) { // Only retrieve the first result. var trip = (await query.ReadNextAsync()).FirstOrDefault(); if (trip != null) { // Retrieve the Consignment record. var document = await container.ReadItemAsync <Consignment>(trip.consignmentId, new Microsoft.Azure.Cosmos.PartitionKey(trip.consignmentId)); var consignment = document.Resource; var updateTrip = false; var updateConsignment = false; // Calculate how far along the vehicle is for this trip. var milesDriven = odometerHigh - trip.odometerBegin; if (milesDriven >= trip.plannedTripDistance) { // The trip is completed! trip.status = WellKnown.Status.Completed; trip.odometerEnd = odometerHigh; trip.tripEnded = DateTime.UtcNow; consignment.status = WellKnown.Status.Completed; // Update the trip and consignment records. updateTrip = true; updateConsignment = true; sendTripAlert = true; } else { if (DateTime.UtcNow >= consignment.deliveryDueDate && trip.status != WellKnown.Status.Delayed) { // The trip is delayed! trip.status = WellKnown.Status.Delayed; consignment.status = WellKnown.Status.Delayed; // Update the trip and consignment records. updateTrip = true; updateConsignment = true; sendTripAlert = true; } } if (trip.tripStarted == null) { // Set the trip start date. trip.tripStarted = DateTime.UtcNow; // Set the trip and consignment status to Active. trip.status = WellKnown.Status.Active; consignment.status = WellKnown.Status.Active; updateTrip = true; updateConsignment = true; sendTripAlert = true; } // Update the trip and consignment records. if (updateTrip) { await container.ReplaceItemAsync(trip, trip.id, new Microsoft.Azure.Cosmos.PartitionKey(trip.partitionKey)); } if (updateConsignment) { await container.ReplaceItemAsync(consignment, consignment.id, new Microsoft.Azure.Cosmos.PartitionKey(consignment.partitionKey)); } // Send a trip alert. if (sendTripAlert) { // Have the HttpClient factory create a new client instance. var httpClient = _httpClientFactory.CreateClient(NamedHttpClients.LogicAppClient); // Create the payload to send to the Logic App. var payload = new LogicAppAlert { consignmentId = trip.consignmentId, customer = trip.consignment.customer, deliveryDueDate = trip.consignment.deliveryDueDate, hasHighValuePackages = trip.packages.Any(p => p.highValue), id = trip.id, lastRefrigerationUnitTemperatureReading = averageRefrigerationUnitTemp, location = trip.location, lowestPackageStorageTemperature = trip.packages.Min(p => p.storageTemperature), odometerBegin = trip.odometerBegin, odometerEnd = trip.odometerEnd, plannedTripDistance = trip.plannedTripDistance, tripStarted = trip.tripStarted, tripEnded = trip.tripEnded, status = trip.status, vin = trip.vin, temperatureSetting = trip.temperatureSetting, recipientEmail = Environment.GetEnvironmentVariable("RecipientEmail") }; var postBody = JsonConvert.SerializeObject(payload); await httpClient.PostAsync(Environment.GetEnvironmentVariable("LogicAppUrl"), new StringContent(postBody, Encoding.UTF8, "application/json")); } } } } } }