Skip to content

Commit

Permalink
feat: set auto number config(#49)
Browse files Browse the repository at this point in the history
Functionality for automatically setting Auto-number seed values on deployment through the config file.
  • Loading branch information
mjahlv authored Apr 15, 2021
1 parent ba06bb5 commit abf5157
Show file tree
Hide file tree
Showing 17 changed files with 620 additions and 3 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ This project's aim is to build a powerful base Package Deployer template that si
- [Import data](#Import-data)
- [Word templates](#Word-templates)
- [Import word templates](#Import-word-templates)
- [Attribute specific functionality](#Attribute-specific-functionality)
- [Set auto-number seed values](#Set-auto-number-seed-values)
- [Mailboxes](#Mailboxes)
- [Update, approve, test and enable shared mailboxes](#Update-approve-test-and-enable-shared-mailboxes)
- [Azure Pipelines](#Azure-pipelines)
Expand Down Expand Up @@ -169,6 +171,34 @@ You can import word templates by adding `<documenttemplate>` elements.
</templateconfig>
```

### Attribute specific functionality

#### Set auto-number seed values

When deploying auto-numbers, seed values can be defined in the template for each entity. These are set post-deployment. Setting the `<autonumberseedvalue>` element determines that the column is an auto-number.

```xml
<templateconfig>
<tables>
<table name="account">
<columns>
<column name="new_accountautonumber" autonumberseedvalue="1"/>
</columns>
</table>
<table name="contact">
<columns>
<column name="new_contactautonumber" autonumberseedvalue="2000"/>
</columns>
</table>
</tables>
</templateconfig>
```
**Important Note**: When you set a seed value, it will reset the next number in the sequence to the seed value. Unless the autonumber column has an alternate key, it will not be enforced as unique. This means you could accidentally reset the count and end up with duplicate auto-number values.

You should set the seed once in the config file and avoid changing it. If you need to change the seed, ensure that it is a higher value than the current value on all target environments.

More information can be read on this functionality here: https://docs.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/create-auto-number-attributes

### Mailboxes

#### Update, approve, test and enable shared mailboxes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ public CrmServiceAdapter(CrmServiceClient crmSvc, ILogger logger)
/// <inheritdoc/>
public Guid? CallerAADObjectId { get => this.crmSvc.CallerAADObjectId; set => this.crmSvc.CallerAADObjectId = value; }

/// <inheritdoc/>
public ExecuteMultipleResponse ExecuteMultiple(IEnumerable<OrganizationRequest> requests, bool continueOnError = true, bool returnResponses = true)
{
var executeMultipleRequest = new ExecuteMultipleRequest
{
Requests = new OrganizationRequestCollection(),
Settings = new ExecuteMultipleSettings
{
ContinueOnError = continueOnError,
ReturnResponses = returnResponses,
},
};
executeMultipleRequest.Requests.AddRange(requests);

return (ExecuteMultipleResponse)this.crmSvc.Execute(executeMultipleRequest);
}

/// <inheritdoc/>
public IEnumerable<Guid> RetrieveSolutionComponentObjectIds(string solutionName, int componentType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ public interface ICrmServiceAdapter : IOrganizationService
/// <returns>An <see cref="ExecuteMultipleResponse"/>.</returns>
ExecuteMultipleResponse UpdateStateAndStatusForEntityInBatch(EntityCollection records, int statecode, int statuscode);

/// <summary>
/// Execute multiple requests.
/// </summary>
/// <param name="requests">The requests.</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, bool continueOnError = true, bool returnResponses = true);

/// <summary>
/// Updates the state and status for an entity.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Capgemini.PowerApps.PackageDeployerTemplate.Config
{
using System.Xml.Serialization;

/// <summary>
/// Auto-number seed configuration element.
/// </summary>
public class ColumnConfig
{
/// <summary>
/// Gets or Sets the logical name of the column we want to configure.
/// </summary>
[XmlAttribute("name")]
public string Name { get; set; }

/// <summary>
/// Gets or Sets the value to set the Auto-number seed to. Setting this will specify this as an auto-number column.
/// </summary>
[XmlAttribute("autonumberseedvalue")]

public string AutonumberSeedAsText
{
get { return this.AutonumberSeedValue.HasValue ? this.AutonumberSeedValue.ToString() : null; }
set { this.AutonumberSeedValue = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?); }
}

/// <summary>
/// Gets or sets a nullable int version of AutonumberSeedValue.
/// </summary>
[XmlIgnore]
public int? AutonumberSeedValue { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Capgemini.PowerApps.PackageDeployerTemplate.Config
{
using System;
using System.Xml.Serialization;

/// <summary>
/// Auto-number seed configuration element.
/// </summary>
public class TableConfig
{
/// <summary>
/// Initializes a new instance of the <see cref="TableConfig"/> class.
/// </summary>
public TableConfig()
{
this.Columns = Array.Empty<ColumnConfig>();
}

/// <summary>
/// Gets or Sets the logical name of the table we want to configure.
/// </summary>
[XmlAttribute("name")]
public string Name { get; set; }

/// <summary>
/// Gets or Sets the logical name of the Auto-number attribute we want to set the seed for.
/// </summary>
[XmlArray("columns")]
[XmlArrayItem("column")]
public ColumnConfig[] Columns { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public TemplateConfig()
this.DocumentTemplates = Array.Empty<DocumentTemplateConfig>();
this.DataImports = Array.Empty<DataImportConfig>();
this.Slas = Array.Empty<SlaConfig>();
this.Tables = Array.Empty<TableConfig>();
}

/// <summary>
Expand Down Expand Up @@ -134,6 +135,16 @@ public TemplateConfig()
[XmlAttribute("activatedeactivateslas")]
public bool ActivateDeactivateSLAs { get; set; }

/// <summary>
/// Gets or sets a list of Tables with Columns that need configuration logic, i.e. Autonumber Seeds.
/// </summary>
/// <value>
/// A list of Tables and their columns to be configured in a given target environment.
/// </value>
[XmlArray("tables")]
[XmlArrayItem("table")]
public TableConfig[] Tables { get; set; }

/// <summary>
/// Load the template config from the specified path.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public abstract class PackageTemplateBase : ImportExtension
private DocumentTemplateDeploymentService documentTemplateSvc;
private SdkStepDeploymentService sdkStepsSvc;
private ConnectionReferenceDeploymentService connectionReferenceSvc;
private TableColumnProcessingService autonumberSeedSettingSvc;
private MailboxDeploymentService mailboxSvc;

/// <summary>
Expand Down Expand Up @@ -205,6 +206,22 @@ protected ConnectionReferenceDeploymentService ConnectionReferenceSvc
}
}

/// <summary>
/// Gets a service that provides functionality relating to setting autonumber seeds.
/// </summary>
protected TableColumnProcessingService AutonumberSeedSettingSvc
{
get
{
if (this.autonumberSeedSettingSvc == null)
{
this.autonumberSeedSettingSvc = new TableColumnProcessingService(this.TraceLoggerAdapter, this.CrmServiceAdapter ?? this.CrmServiceAdapter);
}

return this.autonumberSeedSettingSvc;
}
}

/// <summary>
/// Gets provides deployment functionality relating to mailboxes.
/// </summary>
Expand Down Expand Up @@ -343,6 +360,11 @@ public override bool AfterPrimaryImport()
this.TemplateConfig.DocumentTemplates.Select(d => d.Path),
this.PackageFolderPath);
if (this.TemplateConfig.Tables != null && this.TemplateConfig.Tables.Any())
{
this.AutonumberSeedSettingSvc.ProcessTables(this.TemplateConfig.Tables);
}
this.MailboxSvc.UpdateApproveAndEnableMailboxes(this.MailboxMappings);
if (RunningOnAzureDevOps)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
namespace Capgemini.PowerApps.PackageDeployerTemplate.Services
{
using System;
using System.Collections.Generic;
using System.Linq;
using Capgemini.PowerApps.PackageDeployerTemplate.Adapters;
using Capgemini.PowerApps.PackageDeployerTemplate.Config;
using Capgemini.PowerApps.PackageDeployerTemplate.Extensions;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.Xrm.Sdk;

/// <summary>
/// Service responsible for setting Auto-number seeds.
/// </summary>
public class TableColumnProcessingService
{
private readonly ILogger logger;
private readonly ICrmServiceAdapter crmSvc;

/// <summary>
/// Initializes a new instance of the <see cref="TableColumnProcessingService"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="crmSvc">The <see cref="ICrmServiceAdapter"/>.</param>
public TableColumnProcessingService(ILogger logger, ICrmServiceAdapter crmSvc)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.crmSvc = crmSvc ?? throw new ArgumentNullException(nameof(crmSvc));
}

/// <summary>
/// Processes tables with columns that have specific configurations, such as setting Auto-number seeds.
/// </summary>
/// <param name="tableConfigs">A collection of table elements to process specific attributes for.</param>
public void ProcessTables(IEnumerable<TableConfig> tableConfigs)
{
List<OrganizationRequest> requests = new List<OrganizationRequest>();

// Get all of the Auto-number seed configurations in the collection
requests.AddRange(this.GenerateAutonumberSeedRequests(tableConfigs));

// Check if we have requests that need processing.
if (requests.Count > 0)
{
this.logger.LogInformation("Executing requests for table columns.");
var executeMultipleResponse = this.crmSvc.ExecuteMultiple(requests);

if (executeMultipleResponse.IsFaulted)
{
this.logger.LogError("Error processing requests for table columns");
this.logger.LogExecuteMultipleErrors(executeMultipleResponse);
}
}
else
{
this.logger.LogInformation("No requests for table columns were added.");
}
}

/// <summary>
/// Sets the Auto-number seeds in a given target environment.
/// </summary>
/// <param name="tableConfigs">A collection of table elements to check for Auto-number seed setting configurations.</param>
private List<SetAutoNumberSeedRequest> GenerateAutonumberSeedRequests(IEnumerable<TableConfig> tableConfigs)
{
List<SetAutoNumberSeedRequest> autonumberSeedRequests = new List<SetAutoNumberSeedRequest>();

// Loop through the tables and their columns to find any auto-number configurations.
foreach (TableConfig tableConfig in tableConfigs)
{
foreach (ColumnConfig column in tableConfig.Columns.Where(c => c.AutonumberSeedValue.HasValue && !this.AutonumberSeedAlreadySet(tableConfig.Name, c)))
{
this.logger.LogInformation($"Adding auto-number seed request. Entity Name: {tableConfig.Name}. Auto-number Attribute: {column.Name}. Value: {column.AutonumberSeedValue}");
autonumberSeedRequests.Add(new SetAutoNumberSeedRequest
{
EntityName = tableConfig.Name,
AttributeName = column.Name,
Value = (int)column.AutonumberSeedValue,
});
}
}

// Check if we did not find any autonumber seed configurations and output a message to the log file.
if (autonumberSeedRequests.Count == 0)
{
this.logger.LogInformation("No requests for Auto-number seeds were added.");
}

return autonumberSeedRequests;
}

/// <summary>
/// Checks if the Auto-number seed value is already set to the desired value. If so, we do not want to set the value as it will reset the next number in the sequence to the seed value.
/// </summary>
/// <returns>Boolean indicating if the seed value is already set in the target environment.</returns>
private bool AutonumberSeedAlreadySet(string tableName, ColumnConfig columnConfig)
{
GetAutoNumberSeedRequest request = new GetAutoNumberSeedRequest
{
EntityName = tableName,
AttributeName = columnConfig.Name,
};

GetAutoNumberSeedResponse response = (GetAutoNumberSeedResponse)this.crmSvc.Execute(request);

if (response != null && response.AutoNumberSeedValue == columnConfig.AutonumberSeedValue)
{
this.logger.LogInformation($"Auto-number seed {columnConfig.Name} for {tableName} is already set to value: {columnConfig.AutonumberSeedValue}");
return true;
}
else
{
return false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Linq;
using FluentAssertions;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Xunit;
Expand Down Expand Up @@ -136,5 +137,20 @@ public void PackageTemplateBase_FlowsInPackage_AreActiveAccordingToConfig(string

workflow["statecode"].As<OptionSetValue>().Value.Should().Be(stateCode);
}

[Theory]
[InlineData("account", "pdt_testautonumber", 10000)]
public void PackageTemplateBase_TableColumnProcessing_AutonumberSeedIsSet(string entityName, string attributeName, int expectedValue)
{
var req = new GetAutoNumberSeedRequest
{
EntityName = entityName,
AttributeName = attributeName,
};

GetAutoNumberSeedResponse response = (GetAutoNumberSeedResponse)this.fixture.ServiceClient.Execute(req);

response.AutoNumberSeedValue.Should().Be(expectedValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
$ErrorActionPreference = "Stop"

Install-Module -Name Microsoft.Xrm.Tooling.PackageDeployment.Powershell -Force
Install-Module -Name Microsoft.Xrm.Tooling.PackageDeployment.Powershell -Force -Scope CurrentUser

$connectionString = "Url=$env:CAPGEMINI_PACKAGE_DEPLOYER_TESTS_URL; Username=$env:CAPGEMINI_PACKAGE_DEPLOYER_TESTS_USERNAME; Password=$env:CAPGEMINI_PACKAGE_DEPLOYER_TESTS_PASSWORD; AuthType=OAuth; AppId=51f81489-12ee-4a9e-aaae-a2591f45987d; RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97"
$packageName = "Capgemini.PowerApps.PackageDeployerTemplate.MockPackage.dll"
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,18 @@
datafolderpath="Data/Reference/DefaultedPostDeploy/Extract"
importconfigpath="Data/Reference/DefaultedPostDeploy/ImportConfig.json" />
</dataimports>
<tables>
<table name="account">
<columns>
<column name="pdt_testautonumber" autonumberseedvalue="1"/>
</columns>
</table>
<table name="contact">
<columns>
<column name="pdt_testautonumber" autonumberseedvalue="2000"/>
<column name="pdt_contactcolumnnameonly"/>
</columns>
</table>
</tables>
</templateconfig>
</configdatastorage>
Loading

0 comments on commit abf5157

Please sign in to comment.