diff --git a/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs b/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs index 1cc98c5..a9805b3 100644 --- a/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs +++ b/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; + using Polly; /// /// Deployment functionality relating to processes. @@ -103,7 +104,55 @@ private void SetStates(IEnumerable processes, IEnumerable proces this.logger.LogInformation($"Activating processes as {user}."); } + var requests = this.GetSetStateRequests(processes, processesToDeactivate); + + if (!requests.Any()) + { + return; + } + + this.ExecuteSetStateRequests(requests, user); + } + + private void ExecuteSetStateRequests(IEnumerable requests, string user = null) + { + // Due to unpredictable process dependencies we should retry failed requests until there are zero successful responses. + var remainingRequests = new List(requests); + IEnumerable successfulResponses; + IEnumerable failedResponses; + + do + { + var timeout = 120 + (remainingRequests.Count * 10); + var executeMultipleRes = string.IsNullOrEmpty(user) ? + this.crmSvc.ExecuteMultiple(remainingRequests, true, true, timeout) : this.crmSvc.ExecuteMultiple(remainingRequests, user, true, true, timeout); + + successfulResponses = executeMultipleRes.Responses + .Where(r => r.Fault == null) + .ToList(); + failedResponses = executeMultipleRes.Responses + .Except(successfulResponses) + .ToList(); + remainingRequests = failedResponses + .Select(r => remainingRequests[r.RequestIndex]) + .ToList(); + } + while (successfulResponses.Any() && remainingRequests.Count > 0); + + if (!successfulResponses.Any() && remainingRequests.Any()) + { + foreach (var failedResponse in failedResponses) + { + var failedRequest = (SetStateRequest)remainingRequests[failedResponse.RequestIndex]; + this.logger.LogError($"Failed to set state for process {failedRequest.EntityMoniker.Name} with the following error: {failedResponse.Fault.Message}."); + } + } + } + + private List GetSetStateRequests(IEnumerable processes, IEnumerable processesToDeactivate) + { var requests = new List(); + foreach (var deployedProcess in processes) { var stateCode = new OptionSetValue(Constants.Workflow.StateCodeActive); @@ -133,24 +182,7 @@ private void SetStates(IEnumerable processes, IEnumerable proces }); } - if (!requests.Any()) - { - return; - } - - var timeout = 120 + (requests.Count * 10); - var executeMultipleRes = string.IsNullOrEmpty(user) ? - this.crmSvc.ExecuteMultiple(requests, true, true, timeout) : this.crmSvc.ExecuteMultiple(requests, user, true, true, timeout); - - if (executeMultipleRes.IsFaulted) - { - this.logger.LogError("Error(s) encountered when setting process states."); - foreach (var failedResponse in executeMultipleRes.Responses.Where(r => r.Fault != null)) - { - var failedRequest = (SetStateRequest)requests[failedResponse.RequestIndex]; - this.logger.LogError($"Failed to set state for process {failedRequest.EntityMoniker.Name} with the following error: {failedResponse.Fault.Message}."); - } - } + return requests; } private EntityCollection RetrieveProcesses(IEnumerable names) diff --git a/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs b/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs index 3a28268..dd997d1 100644 --- a/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs +++ b/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs @@ -287,6 +287,7 @@ private void MockExecuteMultipleResponse(ExecuteMultipleResponse response = null if (response == null) { response = new ExecuteMultipleResponse(); + response.Results["Responses"] = new ExecuteMultipleResponseItemCollection(); } var returnResult = this.crmServiceAdapterMock