public async Task OrchestrateAggregateLoad([OrchestrationTrigger] IDurableOrchestrationContext orchestrationContext, ILogger log) { await Task.WhenAll( orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateAuthors), new { }), orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestratePublishers), new { }) ); await Task.WhenAll( orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateBooks), new { })); await Task.WhenAll( orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateOrders), new { }), orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateStoreInventory), new { }) ); await Task.WhenAll( orchestrationContext.CallSubOrchestratorAsync(nameof(OrchestrateCustomers), new { }) ); }
public static Task DurableFunctionWithSubOrchestration( [OrchestrationTrigger] IDurableOrchestrationContext context) { return(context.CallSubOrchestratorAsync(nameof(SubOrchestration), null)); }
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 async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log) { if (!_featureOptions.EnableTimeOffSync) { return; } var teamModel = context.GetInput <TeamModel>(); var weeks = context.CurrentUtcDateTime.Date .Range(_options.PastWeeks, _options.FutureWeeks, _options.StartDayOfWeek); // give the wfm provider connector the opportunity to prepare the data for the sync if necessary await context.CallActivityAsync(nameof(PrepareSyncActivity), new PrepareSyncModel { Type = SyncType.TimeOff, TeamId = teamModel.TeamId, WfmId = teamModel.WfmBuId, FirstWeekStartDate = weeks.Min(), LastWeekStartDate = weeks.Max(), TimeZoneInfoId = teamModel.TimeZoneInfoId }); var weekModels = weeks .Select(startDate => new WeekModel { StartDate = startDate, WfmBuId = teamModel.WfmBuId, TeamId = teamModel.TeamId }); var weekTasks = weekModels .Select(weekModel => context.CallSubOrchestratorAsync <bool>(nameof(TimeOffWeekOrchestrator), weekModel)); var allTasks = Task.WhenAll(weekTasks); bool changesProcessed = false; try { var results = await allTasks; changesProcessed = results.Any(b => b); } finally { // always commit what we have successfully applied to Teams if (_options.DraftShiftsEnabled && changesProcessed) { var shareModel = new ShareModel { TeamId = teamModel.TeamId, StartDate = weekModels.Min(w => w.StartDate), EndDate = weekModels.Max(w => w.StartDate).AddWeek() }; await context.CallActivityAsync(nameof(ShareActivity), shareModel); } } if (allTasks.Status == TaskStatus.Faulted) { log.LogAggregateOrchestrationError(allTasks.Exception, teamModel, nameof(TimeOffOrchestrator)); } }
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context) { var collectionPeriod = await context.CallActivityAsync <CollectionPeriod>(nameof(GetActiveCollectionPeriod), null); if (!context.IsReplaying) { _logger.LogInformation("[IncentivePaymentOrchestrator] Incentive Payment process started for collection period {collectionPeriod}", collectionPeriod); } context.SetCustomStatus("SettingActivePeriodToInProgress"); await context.CallActivityAsync(nameof(SetActivePeriodToInProgress), null); context.SetCustomStatus("GettingPayableLegalEntities"); var payableLegalEntities = await context.CallActivityAsync <List <PayableLegalEntityDto> >(nameof(GetPayableLegalEntities), collectionPeriod); context.SetCustomStatus("CalculatingPayments"); var calculatePaymentTasks = new List <Task>(); foreach (var legalEntity in payableLegalEntities) { var calculatePaymentTask = context.CallSubOrchestratorAsync(nameof(CalculatePaymentsForAccountLegalEntityOrchestrator), new AccountLegalEntityCollectionPeriod { AccountId = legalEntity.AccountId, AccountLegalEntityId = legalEntity.AccountLegalEntityId, CollectionPeriod = collectionPeriod }); calculatePaymentTasks.Add(calculatePaymentTask); } await Task.WhenAll(calculatePaymentTasks); context.SetCustomStatus("GettingUnsentClawbackLegalEntities"); var clawbackLegalEntities = await context.CallActivityAsync <List <ClawbackLegalEntityDto> >(nameof(GetUnsentClawbacks), collectionPeriod); if (!context.IsReplaying) { context.SetCustomStatus("LoggingUnsentClawbacks"); foreach (var legalEntity in clawbackLegalEntities) { _logger.LogInformation($"Unsent clawback for AccountId : {legalEntity.AccountId}, AccountLegalEntityId : {legalEntity.AccountLegalEntityId}, Collection Year : {collectionPeriod.Year}, Period : {collectionPeriod.Period}"); } } context.SetCustomStatus("WaitingForPaymentApproval"); var paymentsApproved = await context.WaitForExternalEvent <bool>("PaymentsApproved"); if (!paymentsApproved) { context.SetCustomStatus("PaymentsRejected"); _logger.LogInformation("[IncentivePaymentOrchestrator] Calculated payments for collection period {collectionPeriod} have been rejected", collectionPeriod); return; } context.SetCustomStatus("SendingPayments"); _logger.LogInformation("[IncentivePaymentOrchestrator] Calculated payments for collection period {collectionPeriod} have been approved", collectionPeriod); var sendPaymentTasks = new List <Task>(); foreach (var legalEntity in payableLegalEntities) { var sendPaymentTask = context.CallActivityAsync(nameof(SendPaymentRequestsForAccountLegalEntity), new AccountLegalEntityCollectionPeriod { AccountId = legalEntity.AccountId, AccountLegalEntityId = legalEntity.AccountLegalEntityId, CollectionPeriod = collectionPeriod }); sendPaymentTasks.Add(sendPaymentTask); } await Task.WhenAll(sendPaymentTasks); context.SetCustomStatus("PaymentsSent"); context.SetCustomStatus("SendingClawbacks"); var sendClawbackTasks = new List <Task>(); foreach (var legalEntity in clawbackLegalEntities) { var sendClawbackTask = context.CallActivityAsync(nameof(SendClawbacksForAccountLegalEntity), new AccountLegalEntityCollectionPeriod { AccountId = legalEntity.AccountId, AccountLegalEntityId = legalEntity.AccountLegalEntityId, CollectionPeriod = collectionPeriod }); sendClawbackTasks.Add(sendClawbackTask); } await Task.WhenAll(sendClawbackTasks); context.SetCustomStatus("ClawbacksSent"); context.SetCustomStatus("CompletingPaymentProcessing"); await context.CallActivityAsync(nameof(CompletePaymentProcess), new CompletePaymentProcessInput { CompletionDateTime = DateTime.UtcNow, CollectionPeriod = collectionPeriod }); context.SetCustomStatus("PaymentProcessingCompleted"); _logger.LogInformation("[IncentivePaymentOrchestrator] Incentive Payment process completed for collection period {collectionPeriod}", collectionPeriod); }
public async Task <SignerResult[]> MainOrchestrator( [OrchestrationTrigger] IDurableOrchestrationContext context) { WorkflowStartModel request = context.GetInput <WorkflowStartModel>(); // Database update, has to be done in an activity await context.CallActivityAsync(nameof(MarkWorkflowStarted), request.RequestId); // there is also a variant that supports retries // Prepare email send tasks string[] signerEmails = request.SignerEmails; var emailSendTasks = new List <Task>(signerEmails.Length); for (int i = 0; i < signerEmails.Length; i++) { Task sendTask = context.CallActivityAsync(nameof(SendPleaseSignEmail), new EmailSendParameters { To = signerEmails[i], Subject = request.Subject, Message = request.Message, RequestId = request.RequestId, DocumentName = request.DocumentName }); emailSendTasks.Add(sendTask); } // Fan out to send emails, activities triggered in parallel await Task.WhenAll(emailSendTasks); // Prepare parallel signing tasks var signingTasks = new List <Task <SignerResult> >(signerEmails.Length); for (int i = 0; i < signerEmails.Length; i++) { Task <SignerResult> signingTask = context.CallSubOrchestratorAsync <SignerResult>(nameof(WaitForSignature.WaitForSign), new WaitForSignParameters { SignerEmail = signerEmails[i], RequestId = request.RequestId }); signingTasks.Add(signingTask); } // Wait for result from each signer, another fan out SignerResult[] results = await Task.WhenAll(signingTasks); // Create signed document if everyone signed if (results.All(r => r.Result == SigningDecision.Signed)) { await context.CallActivityAsync(nameof(CreateSignedDocument), new CreateSignedDocumentParameters { RequestId = request.RequestId, Results = results }); } // Send completion email to all signers var completionEmailSendTasks = new List <Task>(signerEmails.Length); for (int i = 0; i < signerEmails.Length; i++) { Task sendTask = context.CallActivityAsync(nameof(SendCompletionEmail), new SendCompletionEmailParameters { RequestId = request.RequestId, To = signerEmails[i], DocumentName = request.DocumentName }); completionEmailSendTasks.Add(sendTask); } // Fan out to send completion emails await Task.WhenAll(completionEmailSendTasks); // Finally, mark the workflow completed in the DB await context.CallActivityAsync(nameof(MarkWorkflowCompleted), request.RequestId); return(results); }
public static async Task <string> MainOrchestrator( [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log) { DateTime startTime = context.CurrentUtcDateTime; if (!context.IsReplaying) { log.LogInformation($"Main orchestrator (v{AzTwitterSarVersion.get()}), start time {startTime}. Call activity: GetTweets"); } string lastTweetId = context.GetInput <string>(); // 1) Get tweets from Twitter API List <TweetProcessingData> tweetData = await context.CallActivityAsync <List <TweetProcessingData> >( "A_GetTweets", lastTweetId); // The most likely case is that there are NO new tweets, so we provide // the short circuit which skips all the processing in this block, and // thus avoids a lot of replaying by the durable function mechanism. if (tweetData.Count > 0) { if (!context.IsReplaying) { log.LogInformation($"Got {tweetData.Count} new tweets; enter processing sub-orchestration."); } // 2) Process tweets one by one to find interesting ones. var parallelScoringTasks = new List <Task <TweetProcessingData> >(); foreach (var tpd in tweetData) // processes in order { if (!context.IsReplaying) { log.LogInformation($"Call sub-orchestration: P_ProcessTweet for tweet: {tpd.IdStr}"); } Task <TweetProcessingData> processTask = context.CallSubOrchestratorAsync <TweetProcessingData>( "P_ProcessTweet", tpd); parallelScoringTasks.Add(processTask); } await Task.WhenAll(parallelScoringTasks); // Sort the list of analyzed tweets by ascending id (chronologically). List <TweetProcessingData> logList = (from pt in parallelScoringTasks orderby Int64.Parse(pt.Result.IdStr) select pt.Result).ToList(); // Find the tweets that shall be published, chronological order. List <TweetProcessingData> publishList = ( from tpd in logList where tpd.ShallBePublished orderby Int64.Parse(tpd.IdStr) select tpd).ToList(); // Parallel section for postprocessing tasks. { if (!context.IsReplaying) { log.LogInformation($"Publishing {publishList.Count} tweets; logging {logList.Count} tweets."); } List <Task <int> > parallelPostprocessingTasks = new List <Task <int> >(); // We know there is something in the log list, but publishing we // trigger only if there is something to do for this activity. if (publishList.Count > 0) { parallelPostprocessingTasks.Add(context.CallActivityAsync <int>("A_PublishTweets", publishList)); } parallelPostprocessingTasks.Add(context.CallActivityAsync <int>("A_LogTweets", logList)); await Task.WhenAll(parallelPostprocessingTasks); } // We know there has been >= 1 tweet, so we update the last seen id, // which is passed to the next call. lastTweetId = logList[logList.Count - 1].IdStr; } else { if (!context.IsReplaying) { log.LogInformation($"Got no new tweets."); } } DateTime currentTime = context.CurrentUtcDateTime; int delaySeconds = GetDelaySeconds(context, log, startTime, currentTime); if (!context.IsReplaying) { log.LogInformation($"Determined delay: {delaySeconds} seconds after current time {currentTime}."); } if (delaySeconds > 0) { DateTime nextTime = currentTime.AddSeconds(delaySeconds); if (!context.IsReplaying) { log.LogInformation($"Setting wakeup time: {nextTime}."); } await context.CreateTimer(nextTime, CancellationToken.None); context.ContinueAsNew(lastTweetId); } return(lastTweetId); }
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log) { var syncJob = context.GetInput <SyncJob>(); _log.SyncJobProperties = syncJob.ToDictionary(); var runId = syncJob.RunId.GetValueOrDefault(context.NewGuid()); _graphGroup.RunId = runId; List <AzureADUser> distinctUsers = null; if (!context.IsReplaying) { _ = _log.LogMessageAsync(new LogMessage { Message = $"{nameof(OrchestratorFunction)} function started", RunId = runId }); } var sourceGroups = await context.CallActivityAsync <AzureADGroup[]>(nameof(SourceGroupsReaderFunction), new SourceGroupsReaderRequest { SyncJob = syncJob, RunId = runId }); try { if (sourceGroups.Length == 0) { if (!context.IsReplaying) { _ = _log.LogMessageAsync(new LogMessage { RunId = runId, Message = $"None of the source groups in {syncJob.Query} were valid guids. Marking job as errored." }); } await context.CallActivityAsync(nameof(EmailSenderFunction), new EmailSenderRequest { SyncJob = syncJob, RunId = runId }); } else { // Run multiple source group processing flows in parallel var processingTasks = new List <Task <List <AzureADUser> > >(); foreach (var sourceGroup in sourceGroups) { var processTask = context.CallSubOrchestratorAsync <List <AzureADUser> >(nameof(SubOrchestratorFunction), new SecurityGroupRequest { SyncJob = syncJob, SourceGroup = sourceGroup, RunId = runId }); processingTasks.Add(processTask); } var tasks = await Task.WhenAll(processingTasks); var users = new List <AzureADUser>(); foreach (var task in tasks) { users.AddRange(task); } distinctUsers = users.GroupBy(user => user.ObjectId).Select(userGrp => userGrp.First()).ToList(); if (!context.IsReplaying) { _ = _log.LogMessageAsync(new LogMessage { RunId = runId, Message = $"Found {users.Count - distinctUsers.Count} duplicate user(s). Read {distinctUsers.Count} users from source groups {syncJob.Query} to be synced into the destination group {syncJob.TargetOfficeGroupId}." }); } } } catch (Exception ex) { _ = _log.LogMessageAsync(new LogMessage { Message = "Caught unexpected exception, marking sync job as errored. Exception:\n" + ex, RunId = runId }); distinctUsers = null; // make sure this gets thrown to where App Insights will handle it throw; } finally { if (distinctUsers != null) { await context.CallActivityAsync(nameof(UsersSenderFunction), new UsersSenderRequest { SyncJob = syncJob, RunId = runId, Users = distinctUsers }); } else { syncJob.Enabled = false; await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), new JobStatusUpdaterRequest { SyncJob = syncJob, Status = SyncStatus.Error }); } } if (!context.IsReplaying) { _ = _log.LogMessageAsync(new LogMessage { Message = $"{nameof(OrchestratorFunction)} function completed", RunId = runId }); } }
public static async Task <ProcessImageResult> ProcessImage( [OrchestrationTrigger] IDurableOrchestrationContext context) { var image = context.GetInput <string>(); // Sub Orchestrator with fan-out / fan-in pattern // It gets the image tags and Update index info for each tag in parallel var indexTagResults = await context.CallSubOrchestratorAsync <List <string> >( Constants.FunctionsNaming.ProcessImageTagsSubOrchestrator, image); var imageApproved = true; if (!indexTagResults.Contains("Food")) { // Human interaction pattern: we wait for Image human review // usually, we here send first some kind of Notification that Human approval is required // and then we wait for an External Event (or timeout) // Run this to send an external event to this orchestrator (true / false to approve/reject): // curl -X POST -d "true" "http://localhost:7071/runtime/webhooks/durabletask/instances/975c3b80-b0c2-47cc-b045-388a0dc2a7f1/raiseEvent/ApprovalEvent?taskHub=TestHubName&connection=Storage&code=edB8GO8I5bbq3Era86TalmoP6MP2ifnarCfc6vNlvFFb3SZ4VYwNWg==" -H "Content-Type: application/json" using (var timeoutCts = new CancellationTokenSource()) { var dueTime = context.CurrentUtcDateTime.AddSeconds(30); // just for demo var durableTimeout = context.CreateTimer(dueTime, timeoutCts.Token); var approvalEvent = context.WaitForExternalEvent <bool>("ApprovalEvent"); if (approvalEvent == await Task.WhenAny(approvalEvent, durableTimeout)) { timeoutCts.Cancel(); imageApproved = approvalEvent.Result; } else { imageApproved = false; // Monitor Timed out (we consider Image is not approved) } } } if (!imageApproved) { var imageRejectedNotification = await context.CallActivityAsync <string>( Constants.FunctionsNaming.SendRejectNotificationActivity, image); return(new ProcessImageResult { ImageName = image, ImageSize = "0x0", ImageTags = indexTagResults, ErrorDetail = imageRejectedNotification, ResultType = ProcessImageResultType.Rejected }); } // At this point we´ve already processed Image tags, and the Image is validated (it´s a foodie Image!) // Next, we resize the image var resize = await context.CallActivityAsync <string>(Constants.FunctionsNaming.ResizeImageActivity, image); // Process is done. Send a notification and Orchestrator is done var notification = await context.CallActivityAsync <string>(Constants.FunctionsNaming.SendNotificationActivity, image); return(new ProcessImageResult { ImageName = image, ImageTags = indexTagResults, ErrorDetail = string.Empty, ResultType = ProcessImageResultType.Processed, ImageSize = resize }); }
public static async Task <object> ProcessVideo( [OrchestrationTrigger] IDurableOrchestrationContext ctx, ILogger log) { // get the input passed from the StartNewAsync() function var videoLocation = ctx.GetInput <string>(); if (!ctx.IsReplaying) { log.LogInformation("About to call transcode video activity"); } string transcodedLocation = null; string thumbnailLocation = null; string withIntroLocation = null; string approvalResult = "Unknown"; try { // Activity to return the bitrates var transcodeResults = await ctx.CallSubOrchestratorAsync <VideoFileInfo[]>("O_TranscodeVideo", videoLocation); transcodedLocation = transcodeResults .OrderByDescending(r => r.BitRate) .Select(r => r.Location) .FirstOrDefault(); // Durable functions api keeps track what activites has been performed and what has not been if (!ctx.IsReplaying) { log.LogInformation("About to call extract thumbnail activity"); } // Call an activity with retry options thumbnailLocation = await ctx.CallActivityWithRetryAsync <string>("A_ExtractThumbnail", new RetryOptions(TimeSpan.FromSeconds(3), 4) { Handle = ex => ex is InvalidOperationException }, transcodedLocation); if (!ctx.IsReplaying) { log.LogInformation("About to call prepend intro activity"); } withIntroLocation = await ctx.CallActivityAsync <string>("A_PrependIntro", transcodedLocation); // Call an activity without retrying await ctx.CallActivityAsync("A_SendApprovalRequestEmail", new ApprovalInfo() { OrchestrationId = ctx.InstanceId, VideoLocation = withIntroLocation }); // An exemple of a running task without timeout // approvalResult = await ctx.WaitForExternalEvent<string>("ApprovalResult"); using (var cts = new CancellationTokenSource()) { // ctx saved dateTime has to be used to keep the orchestrator deterministic var timeoutAt = ctx.CurrentUtcDateTime.AddSeconds(30); // Puts the orchestration to sleep for x amount of time. Takes CancellationToken as // a secondary argument, to be able to stop it. CancellationToken.None can be passed if not. var timeoutTask = ctx.CreateTimer(timeoutAt, cts.Token); var approvalTaks = ctx.WaitForExternalEvent <string>("ApprovalResult"); // Passes in the result of any of the tasks that has been executed first var winner = await Task.WhenAny(approvalTaks, timeoutTask); if (winner == approvalTaks) { approvalResult = approvalTaks.Result; cts.Cancel(); // cancelling the timeout task } else { approvalResult = "Timed Out!"; } } if (approvalResult == "Approved") { await ctx.CallActivityAsync("A_PublishVideo", withIntroLocation); } else { await ctx.CallActivityAsync("A_RejectVideo", withIntroLocation); } return(new { Transcoded = transcodedLocation, ThumbNail = thumbnailLocation, WithIntro = withIntroLocation, ApprovalResult = approvalResult }); } catch (Exception e) { if (!ctx.IsReplaying) { log.LogInformation($"Caught an error from an activity: {e.Message}"); } await ctx.CallActivityAsync <string>("A_Cleanup", new[] { transcodedLocation, thumbnailLocation, withIntroLocation }); return(new { Error = "Failed to process uploaded video", Message = e.Message }); } }
public static async Task <string> SubOrchestrationTest([OrchestrationTrigger] IDurableOrchestrationContext ctx) { await ctx.CallSubOrchestratorAsync(nameof(NoOp), "NoOpInstanceId", null); return("done"); }
/// <summary> /// Schedules an orchestrator function named <paramref name="functionName"/> for execution. /// </summary> /// <param name="context">The context object.</param> /// <param name="functionName">The name of the orchestrator function to call.</param> /// <param name="input">The JSON-serializeable input to pass to the orchestrator function.</param> /// <returns>A durable task that completes when the called orchestrator function completes or fails.</returns> /// <exception cref="ArgumentException"> /// The specified function does not exist, is disabled, or is not an orchestrator function. /// </exception> /// <exception cref="InvalidOperationException"> /// The current thread is different than the thread which started the orchestrator execution. /// </exception> /// <exception cref="FunctionFailedException"> /// The sub-orchestrator function failed with an unhandled exception. /// </exception> public static Task CallSubOrchestratorAsync(this IDurableOrchestrationContext context, string functionName, object input) { return(context.CallSubOrchestratorAsync <object>(functionName, input)); }
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 }); }
public async Task <GroupMembershipMessageResponse> RunOrchestratorAsync([OrchestrationTrigger] IDurableOrchestrationContext context) { GroupMembership groupMembership = null; GraphUpdaterFunctionRequest graphRequest = null; GroupMembershipMessageResponse messageResponse = null; SyncJob syncJob = null; graphRequest = context.GetInput <GraphUpdaterFunctionRequest>(); groupMembership = JsonConvert.DeserializeObject <GroupMembership>(graphRequest.Message); graphRequest.RunId = groupMembership.RunId; try { syncJob = await context.CallActivityAsync <SyncJob>(nameof(JobReaderFunction), new JobReaderRequest { JobPartitionKey = groupMembership.SyncJobPartitionKey, JobRowKey = groupMembership.SyncJobRowKey, RunId = groupMembership.RunId }); await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function started", SyncJob = syncJob }); messageResponse = await context.CallActivityAsync <GroupMembershipMessageResponse>(nameof(MessageCollectorFunction), graphRequest); if (graphRequest.IsCancelationRequest) { await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"Canceling session {graphRequest.MessageSessionId}", SyncJob = syncJob }); await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey, SyncStatus.Error, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId)); return(messageResponse); } if (messageResponse.ShouldCompleteMessage) { var isValidGroup = await context.CallActivityAsync <bool>(nameof(GroupValidatorFunction), new GroupValidatorRequest { RunId = groupMembership.RunId, GroupId = groupMembership.Destination.ObjectId, JobPartitionKey = groupMembership.SyncJobPartitionKey, JobRowKey = groupMembership.SyncJobRowKey }); if (!isValidGroup) { await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey, SyncStatus.Error, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId)); await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function did not complete", SyncJob = syncJob }); return(messageResponse); } var destinationGroupMembers = await context.CallSubOrchestratorAsync <List <AzureADUser> >(nameof(UsersReaderSubOrchestratorFunction), new UsersReaderRequest { SyncJob = syncJob }); var fullMembership = new GroupMembership { Destination = groupMembership.Destination, IsLastMessage = groupMembership.IsLastMessage, RunId = groupMembership.RunId, SourceMembers = messageResponse.CompletedGroupMembershipMessages.SelectMany(x => x.Body.SourceMembers).ToList(), SyncJobPartitionKey = groupMembership.SyncJobPartitionKey, SyncJobRowKey = groupMembership.SyncJobRowKey }; var deltaResponse = await context.CallActivityAsync <DeltaResponse>(nameof(DeltaCalculatorFunction), new DeltaCalculatorRequest { RunId = groupMembership.RunId, GroupMembership = fullMembership, MembersFromDestinationGroup = destinationGroupMembers, }); if (deltaResponse.GraphUpdaterStatus == GraphUpdaterStatus.Error || deltaResponse.GraphUpdaterStatus == GraphUpdaterStatus.ThresholdExceeded) { var updateRequest = CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey, deltaResponse.SyncStatus, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId); if (deltaResponse.GraphUpdaterStatus == GraphUpdaterStatus.ThresholdExceeded) { updateRequest.ThresholdViolations++; if (updateRequest.ThresholdViolations >= _thresholdConfig.NumberOfThresholdViolationsToDisableJob) { updateRequest.Status = SyncStatus.ThresholdExceeded; } } await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), updateRequest); } if (deltaResponse.GraphUpdaterStatus != GraphUpdaterStatus.Ok) { await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function did not complete", SyncJob = syncJob }); return(messageResponse); } if (!deltaResponse.IsDryRunSync) { await context.CallSubOrchestratorAsync <GraphUpdaterStatus>(nameof(GroupUpdaterSubOrchestratorFunction), CreateGroupUpdaterRequest(syncJob, deltaResponse.MembersToAdd, RequestType.Add, deltaResponse.IsInitialSync)); await context.CallSubOrchestratorAsync <GraphUpdaterStatus>(nameof(GroupUpdaterSubOrchestratorFunction), CreateGroupUpdaterRequest(syncJob, deltaResponse.MembersToRemove, RequestType.Remove, deltaResponse.IsInitialSync)); if (deltaResponse.IsInitialSync) { var groupName = await context.CallActivityAsync <string>(nameof(GroupNameReaderFunction), new GroupNameReaderRequest { RunId = groupMembership.RunId, GroupId = groupMembership.Destination.ObjectId }); var additonalContent = new[] { groupName, groupMembership.Destination.ObjectId.ToString(), deltaResponse.MembersToAdd.Count.ToString(), deltaResponse.MembersToRemove.Count.ToString(), _emailSenderAndRecipients.SupportEmailAddresses }; await context.CallActivityAsync(nameof(EmailSenderFunction), new EmailSenderRequest { ToEmail = deltaResponse.Requestor, CcEmail = _emailSenderAndRecipients.SyncCompletedCCAddresses, ContentTemplate = SyncCompletedEmailBody, AdditionalContentParams = additonalContent, RunId = groupMembership.RunId }); } } var message = GetUsersDataMessage(groupMembership.Destination.ObjectId, deltaResponse.MembersToAdd.Count, deltaResponse.MembersToRemove.Count); await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = message }); await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey, SyncStatus.Idle, groupMembership.MembershipObtainerDryRunEnabled, 0, groupMembership.RunId)); var timeElapsedForJob = (context.CurrentUtcDateTime - deltaResponse.Timestamp).TotalSeconds; _telemetryClient.TrackMetric(nameof(Metric.SyncJobTimeElapsedSeconds), timeElapsedForJob); var syncCompleteEvent = new Dictionary <string, string> { { nameof(SyncJob.TargetOfficeGroupId), groupMembership.Destination.ObjectId.ToString() }, { nameof(SyncJob.Type), deltaResponse.SyncJobType }, { "Result", deltaResponse.SyncStatus == SyncStatus.Idle ? "Success": "Failure" }, { nameof(SyncJob.IsDryRunEnabled), deltaResponse.IsDryRunSync.ToString() }, { nameof(Metric.SyncJobTimeElapsedSeconds), timeElapsedForJob.ToString() }, { nameof(DeltaResponse.MembersToAdd), deltaResponse.MembersToAdd.Count.ToString() }, { nameof(DeltaResponse.MembersToRemove), deltaResponse.MembersToRemove.Count.ToString() }, { nameof(Metric.ProjectedMemberCount), fullMembership.SourceMembers.Count.ToString() } }; _telemetryClient.TrackEvent(nameof(Metric.SyncComplete), syncCompleteEvent); } await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"{nameof(OrchestratorFunction)} function completed", SyncJob = syncJob }); return(messageResponse); } catch (Exception ex) { await context.CallActivityAsync(nameof(LoggerFunction), new LoggerRequest { Message = $"Caught unexpected exception, marking sync job as errored. Exception:\n{ex}", SyncJob = syncJob }); if (groupMembership != null && !string.IsNullOrWhiteSpace(groupMembership.SyncJobPartitionKey) && !string.IsNullOrWhiteSpace(groupMembership.SyncJobRowKey)) { await context.CallActivityAsync(nameof(JobStatusUpdaterFunction), CreateJobStatusUpdaterRequest(groupMembership.SyncJobPartitionKey, groupMembership.SyncJobRowKey, SyncStatus.Error, groupMembership.MembershipObtainerDryRunEnabled, syncJob.ThresholdViolations, groupMembership.RunId)); } throw; } }
private static async Task PerformChangeOfCircumstances(IDurableOrchestrationContext context, ApprenticeshipIncentiveOutput incentive) { await context.CallSubOrchestratorAsync(nameof(ChangeOfCircumstanceOrchestrator), new LearnerChangeOfCircumstanceInput(incentive.Id, incentive.ULN)); }