Skip to content

Commit

Permalink
fix: redundant requests when setting states (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
ewingjm authored Jan 28, 2022
1 parent 5a68747 commit 3bf8742
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,36 @@ public ExecuteMultipleResponse ExecuteMultiple(IEnumerable<OrganizationRequest>
return (ExecuteMultipleResponse)this.crmSvc.Execute(executeMultipleRequest);
}

/// <inheritdoc/>
public ExecuteMultipleResponse ExecuteMultiple(IEnumerable<OrganizationRequest> requests, string username, bool continueOnError = true, bool returnResponses = true)
{
if (requests is null)
{
throw new ArgumentNullException(nameof(requests));
}

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

this.logger.LogDebug($"Executing {requests.Count()} requests as {username}.");

var previousCallerObjectId = this.CallerAADObjectId;
ExecuteMultipleResponse response = null;
try
{
this.CallerAADObjectId = this.RetrieveAzureAdObjectIdByDomainName(username);
response = this.ExecuteMultiple(requests, continueOnError, returnResponses);
}
finally
{
this.CallerAADObjectId = previousCallerObjectId;
}

return response;
}

/// <inheritdoc/>
public IEnumerable<Guid> RetrieveSolutionComponentObjectIds(string solutionName, int componentType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ public interface ICrmServiceAdapter : IOrganizationService
/// <returns>The <see cref="ExecuteMultipleResponse"/>.</returns>
ExecuteMultipleResponse ExecuteMultiple(IEnumerable<OrganizationRequest> requests, bool continueOnError = true, bool returnResponses = true);

/// <summary>
/// Execute multiple requests.
/// </summary>
/// <param name="requests">The requests.</param>
/// <param name="username">The user to impersonate.</param>
/// <param name="continueOnError">Whether to continue on error.</param>
/// <param name="returnResponses">Whether to return responses.</param>
/// <returns>The <see cref="ExecuteMultipleResponse"/>.</returns>
ExecuteMultipleResponse ExecuteMultiple(IEnumerable<OrganizationRequest> requests, string username, bool continueOnError = true, bool returnResponses = true);

/// <summary>
/// Updates the state and status for an entity.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Capgemini.PowerApps.PackageDeployerTemplate/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public static class Fields
/// The name of the process.
/// </summary>
public const string Name = "name";

/// <summary>
/// The name of the process.
/// </summary>
public const string StateCode = "statecode";
}
}

Expand Down Expand Up @@ -204,6 +209,11 @@ public static class Fields
/// The name of the SDK message processing step.
/// </summary>
public const string Name = "name";

/// <summary>
/// The name of the SDK message processing step.
/// </summary>
public const string StateCode = "statecode";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void SetStatesBySolution(IEnumerable<string> solutions, IEnumerable<strin
solutions,
Constants.SolutionComponent.ComponentTypeWorkflow,
Constants.Workflow.LogicalName,
new ColumnSet(Constants.Workflow.Fields.Name)).Entities;
new ColumnSet(Constants.Workflow.Fields.Name, Constants.Workflow.Fields.StateCode)).Entities;

this.SetStates(deployedProcesses, componentsToDeactivate, user);
}
Expand Down Expand Up @@ -103,6 +103,7 @@ private void SetStates(IEnumerable<Entity> processes, IEnumerable<string> proces
this.logger.LogInformation($"Activating processes as {user}.");
}

var requests = new List<OrganizationRequest>();
foreach (var deployedProcess in processes)
{
var stateCode = new OptionSetValue(Constants.Workflow.StateCodeActive);
Expand All @@ -114,27 +115,39 @@ private void SetStates(IEnumerable<Entity> processes, IEnumerable<string> proces
statusCode.Value = Constants.Workflow.StatusCodeInactive;
}

if (stateCode.Value == deployedProcess.GetAttributeValue<OptionSetValue>(Constants.Workflow.Fields.StateCode).Value)
{
this.logger.LogInformation($"Process {deployedProcess[Constants.Workflow.Fields.Name]} already has desired state. Skipping.");
continue;
}

this.logger.LogInformation($"Setting process status for {deployedProcess[Constants.Workflow.Fields.Name]} with statecode {stateCode.Value} and statuscode {statusCode.Value}");

// SetStateRequest is supposedly deprecated but UpdateRequest doesn't work for deactivating active flows
var setStateRequest = new SetStateRequest { EntityMoniker = deployedProcess.ToEntityReference(), State = stateCode, Status = statusCode };

try
{
if (!string.IsNullOrEmpty(user))
requests.Add(
new SetStateRequest
{
this.logger.LogInformation($"Impersonating {user} to activate processes.");
EntityMoniker = deployedProcess.ToEntityReference(),
State = stateCode,
Status = statusCode,
});
}

this.crmSvc.Execute<SetStateResponse>(setStateRequest, user, fallbackToExistingUser: true);
}
else
{
this.crmSvc.Execute(setStateRequest);
}
}
catch (Exception ex)
if (!requests.Any())
{
return;
}

var executeMultipleRes = string.IsNullOrEmpty(user) ?
this.crmSvc.ExecuteMultiple(requests, true, true) : this.crmSvc.ExecuteMultiple(requests, user, true, true);

if (executeMultipleRes.IsFaulted)
{
this.logger.LogError("Error(s) encountered when setting process states.");
foreach (var failedResponse in executeMultipleRes.Responses.Where(r => r.Fault != null))
{
this.logger.LogError(ex, $"Status for process {deployedProcess.Attributes[Constants.Workflow.Fields.Name]} could not be set. {ex.Message}");
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}.");
}
}
}
Expand All @@ -143,7 +156,7 @@ private EntityCollection RetrieveProcesses(IEnumerable<string> names)
{
var query = new QueryExpression(Constants.Workflow.LogicalName)
{
ColumnSet = new ColumnSet(false),
ColumnSet = new ColumnSet(Constants.Workflow.Fields.Name, Constants.Workflow.Fields.StateCode, Constants.Workflow.Fields.Type),
};
query.Criteria.AddCondition(Constants.Workflow.Fields.Name, ConditionOperator.In, names.ToArray<object>());
query.Criteria.AddCondition(Constants.Workflow.Fields.Type, ConditionOperator.Equal, Constants.Workflow.TypeDefinition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using Capgemini.PowerApps.PackageDeployerTemplate.Adapters;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
Expand Down Expand Up @@ -46,7 +47,9 @@ public void SetStatesBySolution(IEnumerable<string> solutions, IEnumerable<strin
solutions,
Constants.SolutionComponent.ComponentTypeSdkStep,
Constants.SdkMessageProcessingStep.LogicalName,
new ColumnSet(Constants.SdkMessageProcessingStep.Fields.Name)).Entities;
new ColumnSet(
Constants.SdkMessageProcessingStep.Fields.Name,
Constants.SdkMessageProcessingStep.Fields.StateCode)).Entities;

this.SetStates(deployedSdkSteps, componentsToDeactivate);
}
Expand Down Expand Up @@ -88,28 +91,55 @@ public void SetStates(IEnumerable<string> componentsToActivate, IEnumerable<stri
this.SetStates(sdkSteps, componentsToDeactivate);
}

private void SetStates(IEnumerable<Entity> sdkSteps, IEnumerable<string> sdkStepsToDeactivate)
private void SetStates(IEnumerable<Entity> sdkSteps, IEnumerable<string> sdkStepsToDeactivate = null)
{
if (sdkSteps is null)
{
return;
}

var requests = new List<OrganizationRequest>();
foreach (var deployedSdkStep in sdkSteps)
{
var stateCode = Constants.SdkMessageProcessingStep.StateCodeActive;
var statusCode = Constants.SdkMessageProcessingStep.StatusCodeActive;
var stateCode = new OptionSetValue(Constants.SdkMessageProcessingStep.StateCodeActive);
var statusCode = new OptionSetValue(Constants.SdkMessageProcessingStep.StatusCodeActive);

if (sdkStepsToDeactivate != null && sdkStepsToDeactivate.Contains(deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]))
{
stateCode = Constants.SdkMessageProcessingStep.StateCodeInactive;
statusCode = Constants.SdkMessageProcessingStep.StatusCodeInactive;
stateCode.Value = Constants.SdkMessageProcessingStep.StateCodeInactive;
statusCode.Value = Constants.SdkMessageProcessingStep.StatusCodeInactive;
}

this.logger.LogInformation($"Setting SDK step status for {deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]} with statecode {stateCode} and statuscode {statusCode}");
if (!this.crmSvc.UpdateStateAndStatusForEntity(Constants.SdkMessageProcessingStep.LogicalName, deployedSdkStep.Id, stateCode, statusCode))
if (stateCode.Value == deployedSdkStep.GetAttributeValue<OptionSetValue>(Constants.SdkMessageProcessingStep.Fields.StateCode).Value)
{
this.logger.LogError($"Status for SDK step {deployedSdkStep.Attributes[Constants.SdkMessageProcessingStep.Fields.Name]} could not be set.");
this.logger.LogInformation($"SDK step {deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]} already has desired state. Skipping.");
continue;
}

this.logger.LogInformation($"Setting SDK step status for {deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]} with statecode {stateCode.Value} and statuscode {statusCode.Value}");
requests.Add(
new SetStateRequest
{
EntityMoniker = deployedSdkStep.ToEntityReference(),
State = stateCode,
Status = statusCode,
});
}

if (!requests.Any())
{
return;
}

var executeMultipleRes = this.crmSvc.ExecuteMultiple(requests, true, true);

if (executeMultipleRes.IsFaulted)
{
this.logger.LogError("Error(s) encountered when setting SDK step 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 SDK step {failedRequest.EntityMoniker.Name} with the following error: {failedResponse.Fault.Message}.");
}
}
}
Expand All @@ -118,7 +148,9 @@ private EntityCollection RetrieveSdkSteps(IEnumerable<string> names)
{
var query = new QueryExpression(Constants.SdkMessageProcessingStep.LogicalName)
{
ColumnSet = new ColumnSet(false),
ColumnSet = new ColumnSet(
Constants.SdkMessageProcessingStep.Fields.Name,
Constants.SdkMessageProcessingStep.Fields.StateCode),
};
query.Criteria.AddCondition(Constants.SdkMessageProcessingStep.Fields.Name, ConditionOperator.In, names.ToArray<object>());

Expand Down
2 changes: 2 additions & 0 deletions templates/build-and-test-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ jobs:
PACKAGEDEPLOYER_SETTINGS_CONNREF_PDT_SHAREDAPPROVALS_D7DCB: $(TestEnvironment.Connection.Approvals)
PACKAGEDEPLOYER_SETTINGS_ENVVAR_PDT_TESTVARIABLE: Any string
PACKAGEDEPLOYER_SETTINGS_CONNBASEURL_pdt_5Fexample-20api: https://anyurl.com
PACKAGEDEPLOYER_SETTINGS_LICENSEDUSERNAME: ${{ parameters.username }}
PACKAGEDEPLOYER_SETTINGS_LICENSEDPASSWORD: ${{ parameters.password }}
inputs:
codeCoverageEnabled: true
platform: '$(buildPlatform)'
Expand Down
Loading

0 comments on commit 3bf8742

Please sign in to comment.