Common Program
- Common programs are written to reuse code across different programs.
- Common programs can be executed from client applications using the special API endpoint
POST programs/{programName}/executeCommonProgram. - The programs called using
executeCommonProgramAPI require authentication and execute in the context of the user whose access token is passed in the request.
Coding guidelines
-
Common program is a class that must implement the interface
ICommonProgramAsyncpublic interface ICommonProgramAsync
{
Task<Object> RunAsync(params Object[] arguments);
}NOTE: Implement ICommonProgramAsync with the RunAsync method to enable asynchronous, scalable trigger logic. Most internal service methods are now async, and their synchronous versions are deprecated and will be removed. Use
RunAsyncto ensure compatibility with these changes. -
Any of the Protrak APIs can be used from within the common program code.
-
A common program can invoke another common program as well.
-
Common program code should validate the
argumentsfor correct number and data type. -
Ensure good documentation is created for common program so that developers know exactly when and how to use it.
-
The program execution API endpoint can consume any Json payload which is passed as
arguments[0]to the programRunAsyncmethod. This payload needs to be validated and parsed by the program code. It is important to validate for the Json properties expected and their data type before attempting to parse, to avoid runtime issues in case the caller does not pass proper payload. See example below.
Typical use cases for Common program
- If the same business logic is required in multiple programs, it is better to create common program to avoid code duplication. This provides for better maintainability of code and easier impact analysis for schema changes.
- To expose custom business logic via
executeCommonProgramAPI to client applications or custom widgets.
Anti-patterns or when not to use Common program
- Common programs should contain truly common business logic. Avoid creating common program for simple wrappers over Protrak API.
Sample Code
public class ConvertToTenantTimezoneCommonProgram : ICommonProgramAsync
{
public IHomeService HomeService { get; set; }
public async Task<Object> RunAsync(params Object[] arguments)
{
if (arguments == null)
{
throw new ArgumentNullException(nameof(arguments));
}
if (arguments.Length < 1 || arguments[0] == null)
{
throw new ArgumentOutOfRangeException(nameof(arguments));
}
try
{
DateTime inputDate = Convert.ToDateTime(arguments[0].ToString());
return new Object[] { await ConvertToTenantTimeZoneAsync(inputDate) };
}
catch (FormatException)
{
throw new ArgumentException(nameof(arguments), "Invalid date format passed.");
}
}
private async Task<DateTime> ConvertToTenantTimeZoneAsync(DateTime inputDate)
{
var tenantSettings = await HomeService.GetTenantSettingsAsync();
var dateTimeFormat = tenantSettings.DateTimeFormat;
var timeZoneId = TimeZoneInfo.GetSystemTimeZones().FirstOrDefault(x => x.DisplayName == dateTimeFormat.TimeZone).Id;
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var dateInTenantTimezone = TimeZoneInfo.ConvertTimeFromUtc(inputDate, timeZone).Date;
return dateInTenantTimezone;
}
}
Note:
The Run method (from ICommonProgram) should only be used if the trigger logic is fully synchronous, does not involve any service/API calls, and consists only lightweight in-memory operations.
Using the common program
var allocationDate = instance.Attributes.FirstOrDefault(a => a.Name == ATTRIBUTE_ALLOCATION_DATE).DateValue.Value;
var convertedDate = (object[])ProgramService.ExecuteCommonProgram("ConvertToTenantTimezoneCommonProgram", allocationDate);
var projectAllocationDate = Convert.ToDateTime(convertedDate[0]).ToString("dd/MM/yyyy");
Working with arguments passed to common program
Extracting query parameters when common program is called from executeCommonProgram API
For a common program being called as follows:
endpoint: /programs/FetchGoalsWeightageAndRatings/executeCommonProgram
payload: {instanceId: "eb7271c6-0561-4431-8ae7-b20b00c0284e"}
Refer below sample code to extract the instance id from the Json payload and convert it into a Guid.
using System.Text.Json;
namespace Prorigo.Demo.Programs
{
public class FetchGoalsWeightageAndRatings : ICommonProgram
{
public Object Run(params Object[] arguments)
{
if (arguments == null || arguments.Length < 1)
{
throw new InvalidDataException("Invalid input");
}
try
{
dynamic instance = arguments[0];
var instanceElement = (JsonElement)instance; //retrieves the `JsonElement` representing the `instanceId` property.
//validate that the `instanceId` property exists and that it's a Guid
if (instanceElement.TryGetProperty("instanceId", out JsonElement instanceIdElement))
{
if (instanceIdElement.ValueKind == JsonValueKind.String && Guid.TryParse(instanceIdElement.GetString(), out Guid instanceId))
{
// Use the instanceId as a Guid
}
else
{
throw new InvalidOperationException("The instanceId property is not a valid Guid.");
}
}
else
{
throw new KeyNotFoundException("The instanceId property was not found.");
}
}
}
}
}