public async Task <bool> OnMessageDispatchedAsync(string engagementAccount, string messageId, string customMessageId, List <string> targets, ConnectorIdentifier connector) { if (!string.IsNullOrEmpty(customMessageId)) { // Insert id mapping await MessageIdMappingTableEntity.InsertOrMergeAsync(this.idMappingTable, new MessageIdMappingTableEntity(engagementAccount, messageId, customMessageId)); } // Get record from summary table var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId); if (entity == null) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for account={engagementAccount} messageId={messageId}"); return(false); } // Calculating the unit according to provider var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName); var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata); if (entity.Units == null) { entity.Units = units; entity.LastUpdateTime = DateTime.UtcNow; await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity); } return(true); }
public static async Task InsertOrMergeAsync(CloudTable table, MessageSummaryTableEntity summaryEntity) { var operations = new[] { TableOperation.InsertOrMerge(new MessageHistoryTableEntity(summaryEntity, false)), TableOperation.InsertOrMerge(new MessageHistoryTableEntity(summaryEntity, true)) }; var tasks = operations.Select(operation => table.ExecuteAsync(operation)); await Task.WhenAll(tasks); }
private async Task UpdateMessageDetailsAsync(string engagementAccount, MessageSummaryTableEntity summary, List <ReportDetail> reports) { var entities = new List <MessageDetailTableEntity>(); var units = summary.Units ?? 1; foreach (var report in reports) { // Duplicate detail record if units is not 1 for (var i = 0; i < units; i++) { entities.Add(new MessageDetailTableEntity(engagementAccount, summary.MessageId, report)); } } await MessageDetailTableEntity.InsertOrMergeBatchAsync(this.detailTable, entities); }
public async Task OnMessageSentAsync(InputMessage message, SmsMessageInfoExtension extension) { // Create record in summary table var summary = new MessageSummaryTableEntity(message, extension); await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, summary); // Create record in history table var historyTable = await this.GetHistoryTableAsync(message.MessageInfo.EngagementAccount); if (historyTable == null) { // Create the table in case any issue in account initialize historyTable = await this.GetHistoryTableAsync(message.MessageInfo.EngagementAccount, true); } await MessageHistoryTableEntity.InsertOrMergeAsync(historyTable, summary); }
public MessageHistoryTableEntity(MessageSummaryTableEntity entity, bool useSendTimeAsPartitionKey) { this.EngagementAccount = entity.EngagementAccount; this.MessageId = entity.MessageId; this.MessageBody = entity.MessageBody; this.Targets = entity.Targets; this.SendTime = entity.SendTime; this.LastUpdateTime = entity.LastUpdateTime; this.MessageCategory = entity.MessageCategory; if (useSendTimeAsPartitionKey) { this.PartitionKey = ToPartitionKey(new DateTimeOffset(this.SendTime)); this.RowKey = this.MessageId; } else { this.PartitionKey = this.MessageId; this.RowKey = this.EngagementAccount; } }
public async Task <bool> OnMessageReportUpdatedAsync(string messageId, string customMessageId, List <ReportDetail> reports, ConnectorIdentifier connector) { if (string.IsNullOrEmpty(messageId) && !string.IsNullOrEmpty(customMessageId)) { // Get id mapping, retry 3 times if cannot found (in case racing with case #2 who has not insert the mapping yet) int retry = 3; while (retry > 0) { messageId = (await MessageIdMappingTableEntity.GetAsync(idMappingTable, customMessageId))?.MessageId; if (!string.IsNullOrEmpty(messageId)) { break; } retry--; await Task.Delay(1000); } if (string.IsNullOrEmpty(messageId)) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for customMessageId={customMessageId}"); return(false); } } // Get record from summary table var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId); if (entity == null) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for messageId={messageId}"); return(false); } // Calculating the unit according to provider // This is for long message that will be charged as multiple messages var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName); var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata); // Report might be ealier than dispatch result, so make sure units is updated if (entity.Units == null) { entity.Units = units; entity.LastUpdateTime = DateTime.UtcNow; await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity); } // Update detail table if (reports != null && reports.Count > 0) { await this.UpdateMessageDetailsAsync(entity.EngagementAccount, entity, reports); } // Get account detail var account = await this.store.GetAccountAsync(entity.EngagementAccount); var succeed = reports.Where(r => r.State == MessageState.DELIVERED).Count(); var failed = reports.Where(r => r.State != MessageState.DELIVERED && r.State != MessageState.UNKNOWN).Count(); // Push metric and billing usage if (Enum.TryParse(entity.MessageCategory, out MessageCategory category)) { if (succeed > 0) { this.metricManager.LogDeliverSuccess(succeed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category); if (Constants.MessageCategoryToUsageTypeMappings.TryGetValue(category, out ResourceUsageType usageType)) { var usage = new ResourceUsageRecord(); usage.EngagementAccount = entity.EngagementAccount; usage.UsageType = usageType; usage.Quantity = succeed * units; await this.billingAgent.StoreBillingUsageAsync(new List <ResourceUsageRecord> { usage }, CancellationToken.None); SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Succeeded, $"Usage pushed to billing service account={entity.EngagementAccount} usageType={usageType} quantity={succeed}x{units}"); } } if (failed > 0) { this.metricManager.LogDeliverFailed(failed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category); } } else { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Failed, $"Failed to parse MessageCategory={entity.MessageCategory} MessageId={entity.MessageId}"); } return(true); }
public static async Task InsertOrMergeAsync(CloudTable table, MessageSummaryTableEntity entity) { var operation = TableOperation.InsertOrMerge(entity); await table.ExecuteAsync(operation); }