public static async Task <int> PeriodicTask(
            [OrchestrationTrigger] IDurableOrchestrationContext ctx,
            ILogger log)
        {
            log = ctx.CreateReplaySafeLogger(log);

            var timesRun = ctx.GetInput <int>();

            timesRun++;
            log.LogInformation($"Starting the PeriodicTask activity {ctx.InstanceId}, {timesRun}");
            await ctx.CallActivityAsync("A_PeriodicActivity", timesRun);

            var nextRun = ctx.CurrentUtcDateTime.AddSeconds(30);
            await ctx.CreateTimer(nextRun, CancellationToken.None);

            ctx.ContinueAsNew(timesRun);
            return(timesRun);
        }
        public async Task RunAsync(
            [OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext,
            ILogger log)
        {
            ILogger replaySafeLogger = orchestrationContext.CreateReplaySafeLogger(log);

            TenantedFunctionData <CreateNotificationsRequest> request = orchestrationContext.GetInput <TenantedFunctionData <CreateNotificationsRequest> >();

            try
            {
                await orchestrationContext.CallActivityAsync(
                    nameof(StartLongRunningOperationActivity),
                    (request.LongRunningOperationId !.Value, request.TenantId));

                // Fan out to create each notification
                string[] correlationIds = new string[request.Payload.CorrelationIds.Length + 1];
                request.Payload.CorrelationIds.CopyTo(correlationIds, 0);
                correlationIds[^ 1] = orchestrationContext.InstanceId;
コード例 #3
0
        public static async Task <List <string> > HelloCities(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger logger)
        {
            logger = context.CreateReplaySafeLogger(logger);
            logger.LogInformation("Starting '{name}' orchestration with ID = 'id'", context.Name, context.InstanceId);

            var outputs = new List <string>
            {
                await context.CallActivityAsync <string>(nameof(Common.SayHello), "Tokyo"),
                await context.CallActivityAsync <string>(nameof(Common.SayHello), "Seattle"),
                await context.CallActivityAsync <string>(nameof(Common.SayHello), "London"),
                await context.CallActivityAsync <string>(nameof(Common.SayHello), "Amsterdam"),
                await context.CallActivityAsync <string>(nameof(Common.SayHello), "Mumbai")
            };

            logger.LogInformation("Finished '{name}' orchestration with ID = 'id'", context.Name, context.InstanceId);
            return(outputs);
        }
コード例 #4
0
        public async Task <string[]> Run(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            var logger     = context.CreateReplaySafeLogger(log);
            var accountIds = context.GetInput <string[]>();

            logger.LogInformation($"Fan out account ids. {ObjectDumper.Dump(accountIds)}");

            var gameIdTasks = new List <Task <string[]> >();

            foreach (var accountId in accountIds)
            {
                var activityInput = (_lolChestConfig.Region, accountId);
                gameIdTasks.Add(context.CallActivityAsync <string[]>("GetGameIds", activityInput));
            }

            var gameIdsOfSummoners = await Task.WhenAll(gameIdTasks);

            logger.LogInformation($"Retrieved game ids of all summoners. {ObjectDumper.Dump(gameIdsOfSummoners)}");

            var allGameIds = gameIdsOfSummoners.SelectMany(x => x).Distinct();

            var commonGameIds = new List <string>();

            foreach (var gameId in allGameIds)
            {
                var isCommonGame = true;
                foreach (var gameIdsOfSummoner in gameIdsOfSummoners)
                {
                    isCommonGame &= gameIdsOfSummoner.Contains(gameId);
                }

                if (isCommonGame)
                {
                    commonGameIds.Add(gameId);
                }
            }

            logger.LogInformation($"Filtered common game ids. {ObjectDumper.Dump(commonGameIds)}");

            return(commonGameIds.ToArray());
        }
コード例 #5
0
        public static async Task TaskExecutionOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            log = context.CreateReplaySafeLogger(log);
            var request       = context.GetInput <TaskExecutionRequest>();
            var taskHierarchy = request.Tasks;

            log.LogWarning("*** {eventName}: {taskId} on compute set {computeSetId}", "TasksStarting", taskHierarchy.Id, taskHierarchy.ComputeSetId);

            foreach (var taskItem in taskHierarchy.TaskItems)
            {
                var message = await context.CallActivityAsync <string>("ExecuteTask", taskItem);
            }

            //log.LogWarning("*** {eventName}: compute set {computeSetId}", "SignalTasksCompleted", taskHierarchy.ComputeSetId);
            var computeSet = context.CreateEntityProxy <IComputeSet>(new EntityId(nameof(ComputeSet), taskHierarchy.ComputeSetId));
            await computeSet.SignalTasksCompleted(request);
        }
コード例 #6
0
        public static async Task PeriodicCrawlExecuter(
            [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger logger)
        {
            logger = context.CreateReplaySafeLogger(logger);

            var dueTime = context.CurrentUtcDateTime.AddMinutes(15);

            dueTime = dueTime.AddSeconds(-dueTime.Second);

            var needCrawlContests = context.GetInput <List <string> >() ?? new List <string>();
            var tasks             = new Task <bool> [needCrawlContests.Count];

            logger.LogInformation($"needCrawlContests : [{string.Join(", ", needCrawlContests)}]");

            for (int i = 0; i < tasks.Length; i++)
            {
                logger.LogInformation($"start updating {needCrawlContests[i]}");
                tasks[i] = context.CallActivityAsync <bool>("UpdateAPerfs", needCrawlContests[i]);
            }
            for (int i = 0; i < tasks.Length; i++)
            {
                await tasks[i];
            }
            logger.LogInformation("completed");

            for (int i = tasks.Length - 1; i >= 0; i--)
            {
                if (!tasks[i].Result)
                {
                    needCrawlContests.RemoveAt(i);
                }
            }

            if (needCrawlContests.Count == 0)
            {
                return;
            }

            await context.CreateTimer(dueTime, CancellationToken.None);

            context.ContinueAsNew(needCrawlContests);
        }
コード例 #7
0
        public async Task <string[]> Run(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            var logger = context.CreateReplaySafeLogger(log);

            logger.LogInformation("Fan out summoner names to retrieve account ids.");

            var accountIdTasks = new List <Task <string> >();

            foreach (var summonerName in _lolChestConfig.SummonerNames)
            {
                var activityInput = (_lolChestConfig.Region, summonerName);
                accountIdTasks.Add(context.CallActivityAsync <string>("GetAccountId", activityInput));
            }

            var accountIds = await Task.WhenAll(accountIdTasks);

            logger.LogInformation($"Retrieved account ids. {ObjectDumper.Dump(accountIds)}");
            return(accountIds);
        }
コード例 #8
0
        public static async Task UpdateHutsOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
        {
            log = context.CreateReplaySafeLogger(log);
            int startHutId = context.GetInput <int>();

            log.LogInformation("Starting orchestrator with startHutId={startHutId}", startHutId);

            var tasks = new List <Task>();

            // Fan-out. Every day we check 1/7 of all hut IDs in the range
            for (int i = startHutId; i <= MaxHutId; i = i + 7)
            {
                tasks.Add(context.CallActivityAsync(nameof(GetHutFromProviderActivity), i));
            }

            // Fan in. Wait for all to be finished
            await Task.WhenAll(tasks);

            log.LogInformation("MaxHutId {MaxHutId} reached. Ending hut updating now.", MaxHutId);
        }
コード例 #9
0
        public async Task Run(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            var logger  = context.CreateReplaySafeLogger(log);
            var gameIds = context.GetInput <string[]>();

            logger.LogInformation($"Fan out game ids to store them in chest. {ObjectDumper.Dump(gameIds)}");

            var addMatchToChestTasks = new List <Task>();

            foreach (var gameId in gameIds)
            {
                var activityInput = (_lolChestConfig.Region, gameId);
                addMatchToChestTasks.Add(context.CallActivityAsync("AddMatchToChest", activityInput));
            }

            await Task.WhenAll(addMatchToChestTasks);

            logger.LogInformation($"Game ids were added to the chest. {ObjectDumper.Dump(gameIds)}");
        }
        public async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext,
            ILogger log)
        {
            ILogger replaySafeLogger = orchestrationContext.CreateReplaySafeLogger(log);

            replaySafeLogger.LogDebug("Starting new TriggerInstancesExecutionOrchestrator instance");

            WorkflowMessageEnvelope envelope =
                orchestrationContext.GetInputWithCustomSerializationSettings <WorkflowMessageEnvelope>(
                    this.serializerSettingsProvider.Instance);

            int pageNumber = envelope.GetWorkflowInstancesPageNumber();

            replaySafeLogger.LogDebug($"Processing trigger {envelope.Trigger.Id} against instances page {pageNumber}");

            string[] instanceIds =
                await orchestrationContext.CallActivityWithCustomSerializationSettingsAsync <WorkflowMessageEnvelope, string[]>(
                    nameof(GetWorkflowInstanceIdsActivity),
                    envelope,
                    this.serializerSettingsProvider.Instance);

            replaySafeLogger.LogDebug($"Retrieved {instanceIds.Length} instance Ids");

            var tasks = new List <Task>(instanceIds.Length);

            foreach (string current in instanceIds)
            {
                envelope.SetWorkflowInstanceId(this.propertyBagFactory, current);
                tasks.Add(orchestrationContext.CallActivityWithCustomSerializationSettingsAsync(
                              nameof(ProcessTriggerActivity),
                              envelope,
                              this.serializerSettingsProvider.Instance));
            }

            await Task.WhenAll(tasks);

            replaySafeLogger.LogDebug($"Processing trigger {envelope.Trigger.Id} against instances page {pageNumber}");
        }
コード例 #11
0
        public static async Task ProcessOrder(
            [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
        {
            log = context.CreateReplaySafeLogger(log);

            var orderId = await context.CallActivityAsync <Guid>(nameof(ProcessOrderActivity.CreateOrder), null);

            await context.CallActivityAsync(nameof(ProcessOrderActivity.UpdateOrderSetStatus), new UpdateOrder { OrderId = orderId, OrchestrationId = context.InstanceId, Status = OrderStatus.WaitForPayment.ToString() });

            await context.CallActivityAsync(nameof(ProcessOrderActivity.StartPayment), new StartPayment { OrderId = orderId, OrchestrationId = context.InstanceId });

            var paymentResult = await WaitForPaymentResultEvent(context, log);

            await context.CallActivityAsync(
                nameof(ProcessOrderActivity.UpdateOrderSetStatus),
                new UpdateOrder
            {
                OrderId         = orderId,
                OrchestrationId = context.InstanceId,
                Status          = paymentResult
            });
        }
コード例 #12
0
        public static async Task <object> ProcessFileOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
        {
            string result1 = null;
            string result2 = null;
            string result3 = null;

            try
            {
                log = context.CreateReplaySafeLogger(log);

                var fileLocation = context.GetInput <string>();

                result1 = await context.CallActivityAsync <string>("CleanData", fileLocation);

                result2 = await context.CallActivityAsync <string>("ApplyRules", result1);

                result3 = await context.CallActivityAsync <string>("ExtractData", result2);

                return(new
                {
                    CleanData = result1,
                    ApplyRules = result2,
                    ExtractData = result3
                });
            }
            catch (Exception ex)
            {
                log.LogError($"Exception Occured: {ex.Message}");
                await context.CallActivityAsync <string>("Cleanup", "temp data");

                return(new
                {
                    Error = "Failed to process file",
                    Message = ex.Message
                });
            }
        }
コード例 #13
0
        public static async Task <string> RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            log = context.CreateReplaySafeLogger(log);
            log.LogInformation("Someone entered meeting. Starting orchestration...");

            var req = context.GetInput <RoomRequest>();

            log.LogInformation($"Getting devices for room {req.RoomId}");

            // Get all devices for the room
            DeviceIds devices = await context.CallActivityAsync <DeviceIds>("GetDevices", req.RoomId);

            var taskList = new List <Task>();

            // Toggle all of the lights
            foreach (var lightId in devices.Lights)
            {
                taskList.Add(
                    context.CallActivityAsync("ToggleLights", new LightToggleRequest(lightId, req.Toggle))
                    );
            }

            // Connect room device IN PARALLEL
            taskList.Add(
                context.CallActivityAsync("ConnectRoom", new ConnectRoomRequest(devices.ConferencePhone, req.Toggle))
                );

            // Wait for all lights and room connected
            await Task.WhenAll(taskList);

            // Notify attendees the room is dialed in
            await context.CallActivityAsync("NotifyAttendees", req);

            return("Room Activated");
        }
コード例 #14
0
        public async Task ReindexInstancesAsync(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger logger)
        {
            EnsureArg.IsNotNull(context, nameof(context));

            logger = context.CreateReplaySafeLogger(logger);
            ReindexInput input = context.GetInput <ReindexInput>();

            // The ID should be a GUID as generated by the trigger, but we'll assert here just to make sure!
            if (!context.HasInstanceGuid())
            {
                return;
            }

            // Fetch the set of query tags that require re-indexing
            IReadOnlyList <ExtendedQueryTagStoreEntry> queryTags = await GetOperationQueryTagsAsync(context, input);

            logger.LogInformation(
                "Found {Count} extended query tag paths to re-index {{{TagPaths}}}.",
                queryTags.Count,
                string.Join(", ", queryTags.Select(x => x.Path)));

            List <int> queryTagKeys = queryTags.Select(x => x.Key).ToList();

            if (queryTags.Count > 0)
            {
                IReadOnlyList <WatermarkRange> batches = await context.CallActivityWithRetryAsync <IReadOnlyList <WatermarkRange> >(
                    nameof(GetInstanceBatchesV2Async),
                    _options.ActivityRetryOptions,
                    BatchCreationArguments.FromOptions(input.Completed?.Start - 1, _options));

                if (batches.Count > 0)
                {
                    // Note that batches are in reverse order because we start from the highest watermark
                    var batchRange = new WatermarkRange(batches[^ 1].Start, batches[0].End);
コード例 #15
0
        public async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext,
            ILogger log)
        {
            ILogger replaySafeLogger = orchestrationContext.CreateReplaySafeLogger(log);

            WorkflowMessageEnvelope envelope =
                orchestrationContext.GetInputWithCustomSerializationSettings <WorkflowMessageEnvelope>(
                    this.serializerSettingsProvider.Instance);

            try
            {
                await orchestrationContext.CallActivityAsync(
                    nameof(StartLongRunningOperationActivity),
                    (envelope.OperationId, envelope.TenantId));

                if (envelope.IsStartWorkflowRequest)
                {
                    replaySafeLogger.LogDebug(
                        $"Received new start workflow request with Id {envelope.StartWorkflowInstanceRequest.RequestId} for workflow {envelope.StartWorkflowInstanceRequest.WorkflowId}");

                    await orchestrationContext.CallActivityWithCustomSerializationSettingsAsync(
                        nameof(CreateWorkflowActivity),
                        envelope,
                        this.serializerSettingsProvider.Instance);
                }
                else
                {
                    replaySafeLogger.LogDebug($"Received new workflow trigger with Id {envelope.Trigger.Id}");

                    int count = await orchestrationContext.CallActivityWithCustomSerializationSettingsAsync <WorkflowMessageEnvelope, int>(
                        nameof(GetWorkflowInstanceCountActivity),
                        envelope,
                        this.serializerSettingsProvider.Instance);

                    int pages = (int)Math.Ceiling((decimal)count / 500);

                    replaySafeLogger.LogDebug($"Found {count} instances that match. Split into {pages} pages for fan-out.");

                    var tasks = new Task[pages];

                    for (int i = 0; i < pages; i++)
                    {
                        envelope.SetWorkflowInstancesPageNumber(this.propertyBagFactory, i);
                        tasks[i] = orchestrationContext.CallSubOrchestratorWithCustomSerializationSettingsAsync(
                            nameof(TriggerInstancesExecutionOrchestrator),
                            envelope,
                            this.serializerSettingsProvider.Instance);
                    }

                    await Task.WhenAll(tasks);

                    replaySafeLogger.LogDebug("All sub-orchestrations complete");
                }

                await orchestrationContext.CallActivityAsync(
                    nameof(CompleteLongRunningOperationActivity),
                    (envelope.OperationId, envelope.TenantId));
            }
            catch (FunctionFailedException x)
            {
                replaySafeLogger.LogError($"Error during orchestration: {x}");

                await orchestrationContext.CallActivityAsync(
                    nameof(FailLongRunningOperationActivity),
                    (envelope.OperationId, envelope.TenantId));

                throw;
            }
        }
        public static async Task RunOrchestration(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            var command       = functionContext.GetInput <ProviderProjectCreateCommand>();
            var commandResult = command.CreateResult();
            var commandLog    = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            using (log.BeginCommandScope(command))
            {
                try
                {
                    functionContext.SetCustomStatus("Deploy resources", commandLog);

                    var deploymentOutput = await functionContext
                                           .CallDeploymentAsync(nameof(ProjectCreateActivity), command.Payload)
                                           .ConfigureAwait(true);

                    if (deploymentOutput.TryGetValue("resourceId", out var resourceId))
                    {
                        functionContext.SetCustomStatus("Updating user permissions", commandLog);

                        await functionContext
                        .CallActivityWithRetryAsync(nameof(ProjectUsersActivity), (command.Payload, resourceId?.ToString()))
                        .ConfigureAwait(true);
                    }

                    commandResult.Result = new ProviderOutput
                    {
                        Properties = deploymentOutput.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString())
                    };
                }
                catch (Exception exc)
                {
                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);

                    throw exc.AsSerializable();
                }
                finally
                {
                    var commandException = commandResult.Errors?.ToException();

                    if (commandException is null)
                    {
                        functionContext.SetCustomStatus($"Command succeeded", commandLog);
                    }
                    else
                    {
                        functionContext.SetCustomStatus($"Command failed: {commandException.Message}", commandLog, commandException);
                    }

                    functionContext.SetOutput(commandResult);
                }
            }
        }
コード例 #17
0
        public static async Task RunOrchestration(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            var command       = functionContext.GetInput <ProviderProjectCreateCommand>();
            var commandResult = command.CreateResult();
            var commandLog    = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            using (log.BeginCommandScope(command))
            {
                try
                {
                    await functionContext
                    .EnsureAuthorizedAsync()
                    .ConfigureAwait(true);

                    await functionContext
                    .CallOperationAsync(nameof(ProjectCreateActivity), command.Payload)
                    .ConfigureAwait(true);

                    await functionContext
                    .CallActivityWithRetryAsync(nameof(ProjectInitializeActivity), command.Payload)
                    .ConfigureAwait(true);

                    await functionContext
                    .CallSubOrchestratorAsync(nameof(ProjectSyncOrchestration), command.Payload)
                    .ConfigureAwait(true);

                    commandResult.Result = new ProviderOutput {
                        Properties = new Dictionary <string, string>()
                    };
                }
                catch (Exception exc)
                {
                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);

                    throw exc.AsSerializable();
                }
                finally
                {
                    var commandException = commandResult.Errors?.ToException();

                    if (commandException is null)
                    {
                        functionContext.SetCustomStatus($"Command succeeded", commandLog);
                    }
                    else
                    {
                        functionContext.SetCustomStatus($"Command failed: {commandException.Message}", commandLog, commandException);
                    }

                    functionContext.SetOutput(commandResult);
                }
            }
        }
        public static async Task <object> ProcessVideo(
            [OrchestrationTrigger] IDurableOrchestrationContext ctx,
            ILogger log)
        {
            log = ctx.CreateReplaySafeLogger(log);
            var videoLocation = ctx.GetInput <string>();

            log.LogInformation("About to call transcode video activity");

            string transcodedLocation = null;
            string thumbnailLocation  = null;
            string withIntroLocation  = null;
            string approvalResult     = "Unknown";

            try
            {
                ctx.SetCustomStatus("transcoding");
                var transcodeResults =
                    await ctx.CallSubOrchestratorAsync <VideoFileInfo[]>("O_TranscodeVideo", videoLocation);

                transcodedLocation = transcodeResults
                                     .OrderByDescending(r => r.BitRate)
                                     .Select(r => r.Location)
                                     .First();

                ctx.SetCustomStatus("extracting thumbnail");
                log.LogInformation("About to call extract thumbnail");

                thumbnailLocation = await
                                    ctx.CallActivityAsync <string>("A_ExtractThumbnail", transcodedLocation);

                ctx.SetCustomStatus("prepending intro");
                log.LogInformation("About to call prepend intro");

                withIntroLocation = await
                                    ctx.CallActivityAsync <string>("A_PrependIntro", transcodedLocation);

                ctx.SetCustomStatus("sending approval request email");
                await ctx.CallActivityAsync("A_SendApprovalRequestEmail", new ApprovalInfo()
                {
                    OrchestrationId = ctx.InstanceId,
                    VideoLocation   = withIntroLocation
                });

                ctx.SetCustomStatus("waiting for email response");
                try
                {
                    approvalResult = await ctx.WaitForExternalEvent <string>("ApprovalResult", TimeSpan.FromSeconds(30));
                }
                catch (TimeoutException)
                {
                    log.LogWarning("Timed out waiting for approval");
                    approvalResult = "Timed Out";
                }

                if (approvalResult == "Approved")
                {
                    ctx.SetCustomStatus("publishing video");
                    await ctx.CallActivityAsync("A_PublishVideo", withIntroLocation);
                }
                else
                {
                    ctx.SetCustomStatus("rejecting video");
                    await ctx.CallActivityAsync("A_RejectVideo", withIntroLocation);
                }
                ctx.SetCustomStatus("finished");
            }
            catch (Exception e)
            {
                log.LogError($"Caught an error from an activity: {e.Message}");

                ctx.SetCustomStatus("error: cleaning up");
                await
                ctx.CallActivityAsync <string>("A_Cleanup",
                                               new[] { transcodedLocation, thumbnailLocation, withIntroLocation });

                ctx.SetCustomStatus("finished with error");

                return(new
                {
                    Error = "Failed to process uploaded video",
                    Message = e.Message
                });
            }

            return(new
            {
                Transcoded = transcodedLocation,
                Thumbnail = thumbnailLocation,
                WithIntro = withIntroLocation,
                ApprovalResult = approvalResult
            });
        }
コード例 #19
0
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext,
            ILogger log)
        {
            if (orchestrationContext is null)
            {
                throw new ArgumentNullException(nameof(orchestrationContext));
            }

            var command       = orchestrationContext.GetInput <ICommand>();
            var commandResult = command.CreateResult();
            var commandLog    = orchestrationContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            try
            {
                orchestrationContext.SetCustomStatus("Auditing command", log);

                await orchestrationContext
                .AuditAsync(command, commandResult)
                .ConfigureAwait(true);

                orchestrationContext.SetCustomStatus("Processing command", log);

                commandResult = await orchestrationContext
                                .CallSubOrchestratorWithRetryAsync <ICommandResult>(OrchestratorCommandOrchestrationHandler.GetCommandOrchestrationName(command), command.CommandId.ToString(), command)
                                .ConfigureAwait(true);
            }
            catch (Exception exc)
            {
                commandResult ??= command.CreateResult();
                commandResult.Errors.Add(exc);
            }
            finally
            {
                try
                {
                    orchestrationContext.SetCustomStatus("Augmenting command result", log);

                    commandResult = await orchestrationContext
                                    .CallActivityWithRetryAsync <ICommandResult>(nameof(CommandResultAugmentActivity), commandResult)
                                    .ConfigureAwait(true);
                }
                catch (Exception exc)
                {
                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);
                }

                orchestrationContext.SetCustomStatus("Auditing command result", log);

                await orchestrationContext
                .AuditAsync(command, commandResult)
                .ConfigureAwait(true);

                var commandException = commandResult.Errors?.ToException();

                if (commandException is null)
                {
                    orchestrationContext.SetCustomStatus($"Command succeeded", log);
                }
                else
                {
                    orchestrationContext.SetCustomStatus($"Command failed: {commandException.Message}", log, commandException);
                }

                orchestrationContext.SetOutput(commandResult);
            }
        }
コード例 #20
0
        public static async Task RunOrchestration(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            (string operationActivityName, object operationActivityInput, string operationInstanceId)
                = functionContext.GetInput <(string, object, string)>();

            var operationLog = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            try
            {
                if (string.IsNullOrEmpty(operationInstanceId))
                {
                    operationInstanceId = await functionContext
                                          .CallActivityWithRetryAsync <string>(operationActivityName, operationActivityInput)
                                          .ConfigureAwait(true);

                    if (!string.IsNullOrEmpty(operationInstanceId))
                    {
                        functionContext.ContinueAsNew((operationActivityName, operationActivityInput, operationInstanceId));
                    }
                }
                else
                {
                    await functionContext
                    .CreateTimer(functionContext.CurrentUtcDateTime.AddSeconds(10), CancellationToken.None)
                    .ConfigureAwait(true);

                    var status = await functionContext
                                 .CallActivityWithRetryAsync <OperationStatus>(nameof(OperationStatusActivity), operationInstanceId)
                                 .ConfigureAwait(true);

                    if (status.IsProgressStatus())
                    {
                        functionContext
                        .ContinueAsNew((operationActivityName, operationActivityInput, operationInstanceId));
                    }
                    else if (status.IsErrorStatus())
                    {
                        var operationError = await functionContext
                                             .CallActivityWithRetryAsync <string>(nameof(OperationErrorActivity), operationInstanceId)
                                             .ConfigureAwait(false);

                        if (!string.IsNullOrEmpty(operationError))
                        {
                            throw new Exception(operationError);
                        }
                    }
                }
            }
            catch (Exception exc)
            {
                operationLog.LogError(exc, $"Orchestration '{nameof(OperationOrchestration)}' failed: {exc.Message}");

                throw exc.AsSerializable();
            }
        }
コード例 #21
0
        public static async Task RunOrchestration(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            var command       = functionContext.GetInput <ProviderProjectCreateCommand>();
            var commandResult = command.CreateResult();
            var commandLog    = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            using (log.BeginCommandScope(command))
            {
                try
                {
                    var deploymentOutput = await functionContext
                                           .CallDeploymentAsync(nameof(ProjectCreateActivity), command.Payload)
                                           .ConfigureAwait(true);

                    commandResult.Result = new ProviderOutput
                    {
                        Properties = deploymentOutput.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString())
                    };

                    var resources = await functionContext
                                    .CallActivityWithRetryAsync <IEnumerable <string> >(nameof(ProjectResourceListActivity), command.Payload)
                                    .ConfigureAwait(true);

                    var tasks = new List <Task>();

                    tasks.AddRange(resources.Select(resource => functionContext.CallActivityWithRetryAsync(nameof(ProjectResourceRolesActivity), (command.Payload, resource))));
                    tasks.AddRange(resources.Select(resource => functionContext.CallActivityWithRetryAsync(nameof(ProjectResourceTagsActivity), (command.Payload, resource))));

                    await Task
                    .WhenAll(tasks)
                    .ConfigureAwait(true);
                }
                catch (Exception exc)
                {
                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);

                    throw exc.AsSerializable();
                }
                finally
                {
                    var commandException = commandResult.Errors?.ToException();

                    if (commandException is null)
                    {
                        functionContext.SetCustomStatus($"Command succeeded", commandLog);
                    }
                    else
                    {
                        functionContext.SetCustomStatus($"Command failed: {commandException.Message}", commandLog, commandException);
                    }

                    functionContext.SetOutput(commandResult);
                }
            }
        }
コード例 #22
0
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,

            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            (string deploymentOwnerInstanceId, string deploymentActivityName, object deploymentActivityInput, string deploymentResourceId, string deploymentOutputEventName)
                = functionContext.GetInput <(string, string, object, string, string)>();

            var deploymentLog = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            try
            {
                if (string.IsNullOrEmpty(deploymentResourceId))
                {
                    functionContext.SetCustomStatus($"Starting deployment using activity '{deploymentActivityName}'", deploymentLog);

                    deploymentResourceId = await functionContext
                                           .CallActivityWithRetryAsync <string>(deploymentActivityName, deploymentActivityInput)
                                           .ConfigureAwait(true);

                    if (!string.IsNullOrEmpty(deploymentResourceId))
                    {
                        functionContext.SetCustomStatus($"Monitoring deployment '{deploymentResourceId}'", deploymentLog);

                        functionContext.ContinueAsNew((deploymentOwnerInstanceId, deploymentActivityName, deploymentActivityInput, deploymentResourceId, deploymentOutputEventName));
                    }
                }
                else
                {
                    await functionContext
                    .CreateTimer(functionContext.CurrentUtcDateTime.AddSeconds(10), CancellationToken.None)
                    .ConfigureAwait(true);

                    var state = await functionContext
                                .CallActivityWithRetryAsync <AzureDeploymentState>(nameof(AzureDeploymentStateActivity), deploymentResourceId)
                                .ConfigureAwait(true);

                    if (state.IsProgressState())
                    {
                        functionContext
                        .ContinueAsNew((deploymentOwnerInstanceId, deploymentActivityName, deploymentActivityInput, deploymentResourceId, deploymentOutputEventName));
                    }
                    else if (state.IsErrorState())
                    {
                        var errors = (await functionContext
                                      .CallActivityWithRetryAsync <IEnumerable <string> >(nameof(AzureDeploymentErrorsActivity), deploymentResourceId)
                                      .ConfigureAwait(true)) ?? Enumerable.Empty <string>();

                        foreach (var error in errors)
                        {
                            deploymentLog.LogError($"Deployment '{deploymentResourceId}' reported error: {error}");
                        }

                        throw new AzureDeploymentException($"Deployment '{deploymentResourceId}' failed", deploymentResourceId, errors.ToArray());
                    }
                    else
                    {
                        var output = await functionContext
                                     .GetDeploymentOutputAsync(deploymentResourceId)
                                     .ConfigureAwait(true);

                        if (!string.IsNullOrEmpty(deploymentOutputEventName))
                        {
                            await functionContext
                            .RaiseEventAsync(deploymentOwnerInstanceId, deploymentOutputEventName, output)
                            .ConfigureAwait(true);
                        }

                        functionContext.SetOutput(output);
                    }
                }
            }
            catch (Exception exc)
            {
                deploymentLog.LogError(exc, $"Orchestration '{nameof(AzureDeploymentOrchestration)}' failed: {exc.Message}");

                throw exc.AsSerializable();
            }
        }
コード例 #23
0
        public static async Task Run(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            [DurableClient] IDurableClient durableClient,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            if (durableClient is null)
            {
                throw new ArgumentNullException(nameof(durableClient));
            }

            var commandMessage = functionContext.GetInput <ProviderCommandMessage>()
                                 ?? throw new ArgumentException("Command message is null", nameof(functionContext));

            var command = commandMessage.Command
                          ?? throw new ArgumentException("Command message does not contain a command", nameof(functionContext));

            var commandResult = command.CreateResult();
            var commandLog    = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            try
            {
                await functionContext
                .AuditAsync(command, commandResult)
                .ConfigureAwait(true);

                var commandOrchestrationName = await functionContext
                                               .CallActivityWithRetryAsync <string>(nameof(ProviderCommandDispatchActivity), command)
                                               .ConfigureAwait(true);

                var commandOrchestrationInstanceId = CommandHandler.GetCommandOrchestrationInstanceId(command);

                if (string.IsNullOrEmpty(commandOrchestrationName))
                {
                    commandLog
                    .LogInformation($"Dispatching command '{command.GetType().FullName}' ({command.CommandId}) >>> FALLBACK ({commandOrchestrationInstanceId})");

                    commandResult = await functionContext
                                    .CallSubOrchestratorWithRetryAsync <ICommandResult>(nameof(ProviderCommandFallbackOrchestration), commandOrchestrationInstanceId, command)
                                    .ConfigureAwait(true);
                }
                else
                {
                    commandLog
                    .LogInformation($"Dispatching command '{command.GetType().FullName}' ({commandMessage.CommandId}) >>> {commandOrchestrationName} ({commandOrchestrationInstanceId})");

                    commandResult = await functionContext
                                    .CallSubOrchestratorWithRetryAsync <ICommandResult>(commandOrchestrationName, commandOrchestrationInstanceId, command)
                                    .ConfigureAwait(true);
                }

                do
                {
                    // there is a chance that the suborchestration used to agument the command result
                    // doesn't reflect the final runtime status (completed / failed / canceled) because
                    // of timing issues in the durable functions runtime. to void a none final runtime
                    // status reported back to the orchestrator we loop / wait for this runtime status.

                    commandResult = await functionContext
                                    .CallActivityWithRetryAsync <ICommandResult>(nameof(ProviderCommandResultAugmentActivity), (commandResult, commandOrchestrationInstanceId))
                                    .ConfigureAwait(true);
                }while (commandResult.RuntimeStatus.IsActive());
            }
            catch (Exception exc)
            {
                commandLog.LogError(exc, $"Processing command '{command.GetType().FullName}' ({command.CommandId}) Failed >>> {exc.Message}");

                commandResult ??= command.CreateResult();
                commandResult.Errors.Add(exc);
            }
            finally
            {
                try
                {
                    await functionContext
                    .CallActivityWithRetryAsync(nameof(ProviderCommandResultSendActivity), (commandResult, commandMessage.CallbackUrl))
                    .ConfigureAwait(true);
                }
                catch (Exception exc)
                {
                    commandLog.LogError(exc, $"Sending result for command '{command.GetType().FullName}' ({command.CommandId}) Failed >>> {exc.Message}");

                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);
                }
                finally
                {
                    await functionContext
                    .AuditAsync(command, commandResult)
                    .ConfigureAwait(true);

                    functionContext.SetOutput(commandResult);
                }
            }
        }
コード例 #24
0
        public async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
        {
            log = context.CreateReplaySafeLogger(log);

            var appointment = context.GetInput <ClientAppointment>();

            // Create confirmation code for appointment, send it to client, and return code as result
            var confirmationCode = await context
                                   .CallActivityAsync <ConfirmationCode>(nameof(AppointmentConfirmationNotificationActivity), appointment.PhoneNumber);

            using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

            var confirmationExpiration  = context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(20), cancellationTokenSource.Token);
            var appointmentConfirmation = context.WaitForExternalEvent <ConfirmationCode>("ConfirmAppointment");

            var tasks = new List <Task> {
                appointmentConfirmation, confirmationExpiration
            };

            // Give client X ammount of time to confirm appointment
            do
            {
                var result = await Task.WhenAny(tasks);

                if (result == appointmentConfirmation)
                {
                    if (appointmentConfirmation.Result.Code == confirmationCode.Code)
                    {
                        log.LogInformation($"Appointment {appointment.AppointmentId} confirmed");
                        break;
                    }
                }
                if (result == confirmationExpiration)
                {
                    log.LogWarning($"Appointment {appointment.AppointmentId} failed to be confirmed");
                    cancellationTokenSource.Cancel();
                }
            } while (!cancellationTokenSource.IsCancellationRequested);

            if (!cancellationTokenSource.IsCancellationRequested)
            {
                var appointmentCancelation = context.WaitForExternalEvent("CancelAppointment");
                var appointmentReminder    = context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(15), cancellationTokenSource.Token);
                // AFter this time expires, we assume that appointment is either currently happening or already happened
                var instanceExpiration = context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(25), cancellationTokenSource.Token);

                tasks = new List <Task> {
                    appointmentCancelation, appointmentReminder, instanceExpiration
                };

                // Wait for appointment reminder/cancelation task completions
                do
                {
                    var result = await Task.WhenAny(tasks);

                    if (result == appointmentCancelation)
                    {
                        log.LogWarning($"Instance {context.InstanceId} canceling appointment");
                        cancellationTokenSource.Cancel();
                    }
                    else if (result == appointmentReminder)
                    {
                        log.LogInformation("Sending appponintment reminder");
                        tasks.Remove(appointmentReminder);
                    }
                    else if (result == instanceExpiration)
                    {
                        cancellationTokenSource.Cancel();
                    }
                }while (!cancellationTokenSource.IsCancellationRequested);
            }
        }
        public static async Task Run(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            [DurableClient] IDurableClient durableClient,
            ILogger log)
        {
            if (functionContext is null)
            {
                throw new ArgumentNullException(nameof(functionContext));
            }

            if (durableClient is null)
            {
                throw new ArgumentNullException(nameof(durableClient));
            }

            var commandMessage = functionContext.GetInput <ProviderCommandMessage>()
                                 ?? throw new ArgumentException("Command message is null", nameof(functionContext));

            var command = commandMessage.Command
                          ?? throw new ArgumentException("Command message does not contain a command", nameof(functionContext));

            var commandResult = command.CreateResult();
            var commandLog    = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            try
            {
                await functionContext
                .AuditAsync(command, commandResult)
                .ConfigureAwait(true);

                var commandOrchestrationName = await functionContext
                                               .CallActivityWithRetryAsync <string>(nameof(ProviderCommandDispatchActivity), command)
                                               .ConfigureAwait(true);

                var commandOrchestrationInstanceId = CommandHandler.GetCommandOrchestrationInstanceId(command);

                if (string.IsNullOrEmpty(commandOrchestrationName))
                {
                    commandLog
                    .LogInformation($"Dispatching command '{command.GetType().FullName}' ({command.CommandId}) >>> FALLBACK ({commandOrchestrationInstanceId})");

                    commandResult = await functionContext
                                    .CallSubOrchestratorWithRetryAsync <ICommandResult>(nameof(ProviderCommandFallbackOrchestration), commandOrchestrationInstanceId, command)
                                    .ConfigureAwait(true);
                }
                else
                {
                    commandLog
                    .LogInformation($"Dispatching command '{command.GetType().FullName}' ({commandMessage.CommandId}) >>> {commandOrchestrationName} ({commandOrchestrationInstanceId})");

                    commandResult = await functionContext
                                    .CallSubOrchestratorWithRetryAsync <ICommandResult>(commandOrchestrationName, commandOrchestrationInstanceId, command)
                                    .ConfigureAwait(true);
                }

                commandResult = await functionContext
                                .CallActivityWithRetryAsync <ICommandResult>(nameof(ProviderCommandResultAugmentActivity), (commandResult, commandOrchestrationInstanceId))
                                .ConfigureAwait(true);
            }
            catch (Exception exc)
            {
                commandLog.LogError(exc, $"Processing command '{command.GetType().FullName}' ({command.CommandId}) Failed >>> {exc.Message}");

                commandResult ??= command.CreateResult();
                commandResult.Errors.Add(exc);
            }
            finally
            {
                try
                {
                    await functionContext
                    .CallActivityWithRetryAsync(nameof(ProviderCommandResultSendActivity), (commandResult, commandMessage.CallbackUrl))
                    .ConfigureAwait(true);
                }
                catch (Exception exc)
                {
                    commandLog.LogError(exc, $"Sending result for command '{command.GetType().FullName}' ({command.CommandId}) Failed >>> {exc.Message}");

                    commandResult ??= command.CreateResult();
                    commandResult.Errors.Add(exc);
                }
                finally
                {
                    await functionContext
                    .AuditAsync(command, commandResult)
                    .ConfigureAwait(true);

                    functionContext.SetOutput(commandResult);
                }
            }
        }
コード例 #26
0
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext,
            ILogger log)
        {
            if (orchestrationContext is null)
            {
                throw new ArgumentNullException(nameof(orchestrationContext));
            }

            var functionLog = orchestrationContext.CreateReplaySafeLogger(log ?? NullLogger.Instance);

            var subscriptionId        = orchestrationContext.GetInput <Guid>();
            var resourceId            = new AzureResourceIdentifier(subscriptionId);
            var initializationTimeout = TimeSpan.FromMinutes(5);

            try
            {
                var currentSubscriptionVersion = await orchestrationContext
                                                 .GetSubscriptionVersionAsync(subscriptionId)
                                                 .ConfigureAwait(true);

                if (currentSubscriptionVersion != TargetSubscriptionVersion)
                {
                    using (await orchestrationContext.LockAzureResourceAsync(resourceId).ConfigureAwait(true))
                    {
                        currentSubscriptionVersion = await orchestrationContext
                                                     .GetSubscriptionVersionAsync(subscriptionId)
                                                     .ConfigureAwait(true);

                        if (currentSubscriptionVersion != TargetSubscriptionVersion)
                        {
                            // we have to offload the subscription initialization deployment to
                            // an independant orchestration (StartDeploymentAsync) as we initiate
                            // the deployment inside a critical section. this doesn't allow us
                            // to run the deploy as a nested orchestration (CallDeploymentAsync).

                            var deploymentOutputEventName = subscriptionId.ToString();

                            _ = await orchestrationContext
                                .StartDeploymentAsync(nameof(ProjectSubscriptonInitializeActivity), subscriptionId, deploymentOutputEventName)
                                .ConfigureAwait(true);

                            await orchestrationContext
                            .WaitForExternalEvent(deploymentOutputEventName, initializationTimeout)
                            .ConfigureAwait(true);

                            _ = await orchestrationContext
                                .SetSubscriptionVersionAsync(subscriptionId, TargetSubscriptionVersion)
                                .ConfigureAwait(true);
                        }
                    }
                }
            }
            catch (TimeoutException exc)
            {
                functionLog.LogError(exc, $"Failed to initialize subscription '{subscriptionId}' within a {initializationTimeout} timeout");
            }
            catch (Exception exc)
            {
                functionLog.LogError(exc, $"Failed to initialize subscription '{subscriptionId}': {exc.Message}");
            }
        }
コード例 #27
0
        public async Task Run(
            [OrchestrationTrigger] IDurableOrchestrationContext functionContext,
            ILogger logger
            )
        {
            log = functionContext.CreateReplaySafeLogger(logger);
            var data = functionContext.GetInput <string>();

            //----------------------------------------------------------------
            // Verify Email

            log.LogInformation("Calling SendEmailActivity");
            functionContext.SetCustomStatus("SendingEmail");
            try
            {
                data = await functionContext.CallActivityAsync <string>(
                    "SendEmailActivity",
                    data
                    );
            }
            catch (Exception)
            {
                log.LogInformation("SendEmailActivity failed; exiting.");
                return;
            }

            log.LogInformation("Waiting for Email verification");
            functionContext.SetCustomStatus("AwaitingEmailVerification");
            bool emailVerified = await functionContext.WaitForExternalEvent <bool>("EmailVerified");

            log.LogInformation("Email verification received");


            //----------------------------------------------------------------
            // Verify SMS

            log.LogInformation("Calling SendSmsActivity");
            functionContext.SetCustomStatus("SendingSMS");
            try
            {
                data = await functionContext.CallActivityAsync <string>(
                    "SendSmsActivity",
                    data
                    );
            }
            catch (Exception)
            {
                log.LogInformation("SendSmsActivity failed; exiting.");
                return;
            }

            log.LogInformation("Waiting for SMS verification");
            functionContext.SetCustomStatus("AwaitingSmsVerification");
            bool smsVerified = await functionContext.WaitForExternalEvent <bool>("SmsVerified");

            log.LogInformation("Sms verification received");


            // -----------------------------------------------------
            // Clean up
            var reg = UserRegistration.FromJson(data);

            new OrchestrationEntity().Delete("RegisterOrchestrator", functionContext.InstanceId);
            new RegistrationEntity().Delete("RegisterOrchestrator", functionContext.InstanceId);
            log.LogInformation("Orchestrator done");
        }
コード例 #28
0
        public static async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
        {
            #region null checks
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (log is null)
            {
                throw new ArgumentNullException(nameof(log));
            }
            #endregion

            log = context.CreateReplaySafeLogger(log);

            var catalogueDownloadInfo = context.GetInput <SaleFinderCatalogueDownloadInformation>();

            var scanStateId = ICatalogueScanState.CreateId(new CatalogueScanStateKey(CatalogueType, catalogueDownloadInfo.Store, catalogueDownloadInfo.SaleId.ToString(CultureInfo.InvariantCulture)));
            var scanState   = context.CreateEntityProxy <ICatalogueScanState>(scanStateId);

            using (await context.LockAsync(scanStateId).ConfigureAwait(true))
            {
                #region Check and update the catalogue's scan state
                context.SetCustomStatus("CheckingState");
                log.LogDebug($"Checking state - {scanStateId.EntityKey}");

                var state = await scanState.GetState().ConfigureAwait(true);

                if (state != ScanState.NotStarted)
                {
                    log.LogInformation($"Catalogue {scanStateId.EntityKey} already in state {state}, skipping scan.");
                    context.SetCustomStatus("Skipped");
                    return;
                }

                await scanState.UpdateState(ScanState.InProgress).ConfigureAwait(true);

                #endregion

                #region Download catalogue
                context.SetCustomStatus("Downloading");
                log.LogDebug($"Downloading - {scanStateId.EntityKey}");

                var downloadedCatalogue = await context.CallActivityAsync <Catalogue>(SaleFinderFunctionNames.DownloadSaleFinderCatalogue, catalogueDownloadInfo).ConfigureAwait(true);

                #endregion

                #region Filter catalouge items
                context.SetCustomStatus("Filtering");
                log.LogDebug($"Filtering - {scanStateId.EntityKey}");

                var itemTasks = downloadedCatalogue.Items
                                .Select(item => context.CallActivityAsync <CatalogueItem?>(CoreFunctionNames.FilterCatalogueItem, item))
                                .ToList();

                await Task.WhenAll(itemTasks).ConfigureAwait(true);

                #endregion

                #region Send digest email
                context.SetCustomStatus("SendingDigestEmail");
                log.LogDebug($"Sending digest email - {scanStateId.EntityKey}");

                var filteredItems = itemTasks
                                    .Where(task => task.Result != null)
                                    .Select(task => task.Result !)
                                    .ToList();

                if (filteredItems.Any())
                {
                    var filteredCatalogue = new Catalogue(downloadedCatalogue.Store, downloadedCatalogue.StartDate, downloadedCatalogue.EndDate, filteredItems);

                    await context.CallActivityAsync(CoreFunctionNames.SendCatalogueDigestEmail, filteredCatalogue).ConfigureAwait(true);
                }
                else
                {
                    log.LogInformation($"Catalogue {scanStateId.EntityKey} had no matching items, skipping digest email.");
                }
                #endregion

                #region Update catalogue's scan state
                context.SetCustomStatus("UpdatingState");
                log.LogDebug($"Updating state - {scanStateId.EntityKey}");

                await scanState.UpdateState(ScanState.Completed).ConfigureAwait(true);

                #endregion

                log.LogDebug($"Completed - {scanStateId.EntityKey}");
                context.SetCustomStatus("Completed");
            }
        }