Hi,
I'm experimenting with the ResourceFieldDescriptor, and i was wondering if there is a way to only show the available resources in that list for a certain time range?
This button is part of the DOM definition as I only want to see this button in certain DOM states & in the script which is triggered from the DOM button I immediately know the id of the DOM where I want to store the selected resources again from my interactive automation (would work with a low code app button as well but is a bit less intuitive)
Do note that you need to set the DOM action to interactive before this is possible
https://docs.dataminer.services/user-guide/Advanced_Modules/DOM/DOM_actions.html
This is also possible to set with the DOM editor script on actions
This is some example code from Empower to select some available resources from a certain resource pool & store them back in DOM
<?xml version="1.0" encoding="utf-8" ?>
<DMSScript options="272" xmlns="http://www.skyline.be/automation">
<Name>ServiceOrderApp_SelectAvailableResources</Name>
<Description />
<Type>Automation</Type>
<Author>SKYLINE2\ThomasGH</Author>
<CheckSets>FALSE</CheckSets>
<Folder>DOM_ServiceOrderApp</Folder>
<Protocols>
</Protocols>
<Memory>
</Memory>
<Parameters>
</Parameters>
<Script>
<Exe id="1" type="csharp">
<Value>
<![CDATA[using Skyline.DataMiner.Automation;
using Skyline.DataMiner.Net.Apps.DataMinerObjectModel;
using Skyline.DataMiner.Net.Apps.DataMinerObjectModel.Actions;
using Skyline.DataMiner.Net.Apps.Sections.Sections;
using Skyline.DataMiner.Net.Messages;
using Skyline.DataMiner.Net.Messages.SLDataGateway;
using Skyline.DataMiner.Net.ResourceManager.Objects;
using Skyline.DataMiner.Net.Sections;
using System;
using System.Collections.Generic;
using System.Linq;
using Skyline.DataMiner.DataMinerSolutions.ProcessAutomation.MessageHandler;
using Skyline.DataMiner.DataMinerSolutions.ProcessAutomation.Common.Objects.Exceptions;
namespace DataMiner_Empower_DOM_2023.ServiceOrderApp_SelectAvailableResources
{
public class Script
{
private const string PoolName = "Server Capacity";
private const string ModuleId = "process_automation";
private const string StartTimeFieldName = "Start time";
private const string EndTimeFieldName = "End time";
private const string SelectedResourcesFieldName = "Selected resources";
private const string OrderIdFieldName = "Order ID";
private const string BookingDetailsSectionDefinitionName = "Booking details";
private const string OrderDetailsSectionDefinitionName = "Order details";
private const string NewToWaitingForApprovalTransitionId = "new_to_waiting_for_approval";
private const string PaProcessName = "Service order";
private static readonly TimeSpan OrderMinimumStartOffset = TimeSpan.FromMinutes(30);
private static readonly TimeSpan OrderMinimumDuration = TimeSpan.FromMinutes(5);
private IEngine _engine;
private DomHelper _domHelper;
private ResourceManagerHelper _rmHelper;
private SectionDefinition _bookingDetailsSectionDefinition;
public void Run(Engine engine)
{
// Not doing anything
}
{
try
{
Setup(engine);
InnerRun(context);
}
catch (Exception ex)
{
FailPopUp(ex.Message);
}
}
private void Setup(IEngine engine)
{
_engine = engine;
_domHelper = new DomHelper(_engine.SendSLNetMessages, ModuleId)
{
SectionDefinitions =
{
ThrowExceptionsOnErrorData = false
},
DomDefinitions = { ThrowExceptionsOnErrorData = false },
DomBehaviorDefinitions = { ThrowExceptionsOnErrorData = false },
DomInstances = { ThrowExceptionsOnErrorData = false },
DomTemplates = { ThrowExceptionsOnErrorData = false },
DomInstanceHistory = { ThrowExceptionsOnErrorData = false }
};
_rmHelper = new ResourceManagerHelper(_engine.SendSLNetSingleResponseMessage);
}
private void InnerRun(ExecuteScriptDomActionContext context)
{
// Check the provided ExecuteScriptDomActionContext
var domInstanceId = ValidateExecuteScriptDomActionContext(context);
if (domInstanceId == null)
{
return;
}
// Retrieve the DomInstance
var domInstance = RetrieveDomInstance(domInstanceId);
if (domInstance == null)
{
FailPopUp($"Retrieving the DomInstance with ID '{domInstanceId}' returned a null object");
return;
}
// Retrieve the SectionDefinition
_bookingDetailsSectionDefinition = GetSectionDefinitionByName(BookingDetailsSectionDefinitionName);
if (_bookingDetailsSectionDefinition == null)
{
FailPopUp($"The SectionDefinition to store the selected resources was not found on the system. (Name: {BookingDetailsSectionDefinitionName})");
return;
}
// Stitch the DomInstance so we have easy access to the configuration objects
LogInformationEvent($"Stitching DomInstance '{domInstance.ID.Id}'");
_domHelper.StitchDomInstances(new List<DomInstance> { domInstance });
// Retrieve start and end time from DomInstance
var startTimeFieldValue = GetFieldValue<DateTime>(domInstance, OrderDetailsSectionDefinitionName, StartTimeFieldName);
if (startTimeFieldValue == null)
{
FailPopUp($"Retrieving value for field '{StartTimeFieldName}' on DomInstance '{domInstance.ID.Id}' returned null");
return;
}
var startTime = startTimeFieldValue.Value;
// Check if the start time is starting at least OrderMinimumStartOffset from now
if (DateTime.UtcNow.Add(OrderMinimumStartOffset) > startTime)
{
FailPopUp($"Please review the start time of your order.{Environment.NewLine}Your order should start in at least {OrderMinimumStartOffset.TotalMinutes} minutes and have a {OrderMinimumDuration.TotalMinutes} minutes minimum duration.");
return;
}
var endTimeFieldValue = GetFieldValue<DateTime>(domInstance, OrderDetailsSectionDefinitionName, EndTimeFieldName);
if (endTimeFieldValue == null)
{
FailPopUp($"Retrieving value for field '{EndTimeFieldName}' on DomInstance '{domInstance.ID.Id}' returned null");
return;
}
var endTime = endTimeFieldValue.Value;
// Check if the start time is not bigger than the end time and has a OrderMinimumDuration
if (startTime.Add(OrderMinimumDuration) > endTime)
{
FailPopUp($"Please review the end time of your order.{Environment.NewLine}Your order should start in at least {OrderMinimumStartOffset.TotalMinutes} minutes and have a {OrderMinimumDuration.TotalMinutes} minutes minimum duration.");
return;
}
// Retrieve eligible resources
var resources = GetEligibleResources(startTime, endTime);
if (resources == null)
{
return;
}
// Let the user select resources
resources = resources.OrderBy(x => x.Name).ToList();
var selectedResources = DrawUi(resources);
if (!selectedResources.Any())
{
FailPopUp("No resources were selected");
return;
}
// Update DomInstance
if (!TryUpdateResourcesOnDomInstance(domInstance, selectedResources))
{
return;
}
// Transition the DomInstance
_domHelper.DomInstances.DoStatusTransition(domInstance.ID, NewToWaitingForApprovalTransitionId);
var traceData = _domHelper.DomInstances.GetTraceDataLastCall();
if (!traceData.HasSucceeded())
{
FailPopUp($"Failed to transition the DomInstance: {traceData}");
return;
}
// Push a PA token
PushPaToken(domInstance);
}
private DomInstanceId ValidateExecuteScriptDomActionContext(ExecuteScriptDomActionContext context)
{
if (context == null)
{
FailPopUp($"The provided {nameof(ExecuteScriptDomActionContext)} is null");
return null;
}
if (context.ActionId == null)
{
FailPopUp($"Unable to retrieve the 'ActionId' on the {nameof(ExecuteScriptDomActionContext)}");
return null;
}
if (context.ContextId == null)
{
FailPopUp($"Unable to retrieve the 'ContextId' on the {nameof(ExecuteScriptDomActionContext)}");
return null;
}
var domInstanceId = context.ContextId as DomInstanceId;
if (domInstanceId == null)
{
FailPopUp($"The provided 'ContextId' is not a '{nameof(DomInstanceId)}'");
return null;
}
if (domInstanceId.ModuleId != ModuleId)
{
FailPopUp($"The provided DomInstance belongs to module '{domInstanceId.ModuleId}', although it should belong to module '{ModuleId}'");
return null;
}
if (domInstanceId.Id == Guid.Empty)
{
FailPopUp($"The provided DomInstance doesn't have a valid ID: {domInstanceId.Id}");
return null;
}
return domInstanceId;
}
private DomInstance RetrieveDomInstance(DomInstanceId domInstanceId)
{
var domInstance = _domHelper.DomInstances.Read(DomInstanceExposers.Id.Equal(domInstanceId)).FirstOrDefault();
var traceData = _domHelper.DomInstances.GetTraceDataLastCall();
if (!traceData.HasSucceeded())
{
FailPopUp($"Unable to retrieve DomInstance with ID '{domInstanceId}': {traceData}");
return null;
}
return domInstance;
}
private ValueWrapper<T> GetFieldValue<T>(DomInstance domInstance, string sectionDefinitionName, string fieldName)
{
foreach (var section in domInstance.Sections)
{
var sectionDefinition = section.GetSectionDefinition();
if (sectionDefinition.GetName() != sectionDefinitionName)
{
break;
}
var fields = sectionDefinition.GetAllFieldDescriptors();
foreach (var field in fields)
{
if (field.Name.Equals(fieldName))
{
return domInstance.GetFieldValue<T>(sectionDefinition, field);
}
}
}
return null;
}
private List<Resource> GetEligibleResources(DateTime start, DateTime end)
{
var pool = GetResourcePool(PoolName);
if (pool == null)
{
return null;
}
LogInformationEvent($"Retrieving the eligible resources in the timespan [{start.ToLocalTime()}, {end.ToLocalTime()}]");
var context = new EligibleResourceContext()
{
ResourceFilter = ResourceExposers.PoolGUIDs.Contains(pool.ID),
TimeRange = new Skyline.DataMiner.Net.Time.TimeRangeUtc(start, end)
};
var result = _rmHelper.GetEligibleResources(context);
var traceData = _rmHelper.GetTraceDataLastCall();
if (!traceData.HasSucceeded())
{
FailPopUp($"Retrieving the eligible resources failed: {traceData}");
return null;
}
if (result == null)
{
FailPopUp($"Retrieving the eligible resources returned a null object");
return null;
}
if (result.EligibleResources.Any())
{
return result.EligibleResources;
}
FailPopUp("There are no eligible resources on the system");
return null;
}
private List<Resource> DrawUi(List<Resource> resources)
{
var uiBuilder = new UIBuilder()
{
Title = "Resource selection",
RowDefs = "a;a;a",
ColumnDefs = "a",
RequireResponse = true
};
var info = new UIBlockDefinition()
{
Type = UIBlockType.StaticText,
ColumnSpan = 1,
Row = 0,
Column = 0,
Text = "Please select the resources you want to book and click the Apply button"
};
uiBuilder.AppendBlock(info);
var list = new UIBlockDefinition()
{
Type = UIBlockType.CheckBoxList,
Row = 1,
Column = 0,
DestVar = "resources"
};
foreach (var resource in resources)
{
list.AddCheckBoxListOption(resource.Name);
}
uiBuilder.AppendBlock(list);
var applyButton = new UIBlockDefinition()
{
Type = UIBlockType.Button,
Row = 2,
Column = 0,
WantsOnChange = true,
Text = "Apply"
};
uiBuilder.AppendBlock(applyButton);
var results = _engine.ShowUI(uiBuilder);
return resources.Where(x => results.GetChecked("resources", x.Name)).ToList();
}
private bool TryUpdateResourcesOnDomInstance(DomInstance domInstance, List<Resource> resources)
{
var fieldDescriptor = _bookingDetailsSectionDefinition.GetAllFieldDescriptors()
.FirstOrDefault(one => one.Name == SelectedResourcesFieldName);
if (fieldDescriptor == null)
{
FailPopUp($"No FieldDescriptor was found with name '{SelectedResourcesFieldName}' " +
$"on the SectionDefinition with ID '{_bookingDetailsSectionDefinition.GetID().Id}'");
return false;
}
domInstance.AddOrUpdateListFieldValue(_bookingDetailsSectionDefinition, fieldDescriptor, resources.Select(x => x.ID).ToList());
_domHelper.DomInstances.Update(domInstance);
var traceData = _domHelper.DomInstances.GetTraceDataLastCall();
if (!traceData.HasSucceeded())
{
FailPopUp($"Unable to update the '{SelectedResourcesFieldName}' field on DomInstance '{domInstance.ID.Id}': {traceData}");
return false;
}
return true;
}
private ResourcePool GetResourcePool(string poolName)
{
var retrievedPool = _rmHelper.GetResourcePools(new ResourcePool() { Name = poolName }).FirstOrDefault();
var traceData = _rmHelper.GetTraceDataLastCall();
if (!traceData.HasSucceeded())
{
FailPopUp($"Unable to retrieve pool '{poolName}': {traceData}");
return null;
}
if (retrievedPool == null)
{
FailPopUp($"Retrieving pool with name '{poolName}' returned null");
return null;
}
return retrievedPool;
}
private void FailPopUp(string message)
{
var uiBuilder = new UIBuilder()
{
Title = "Resource selection failed",
RowDefs = "a;a",
ColumnDefs = "a",
RequireResponse = true
};
var info = new UIBlockDefinition()
{
Type = UIBlockType.StaticText,
ColumnSpan = 1,
Row = 0,
Column = 0,
Text = message
};
uiBuilder.AppendBlock(info);
var applyButton = new UIBlockDefinition()
{
Type = UIBlockType.Button,
Row = 2,
Column = 0,
WantsOnChange = true,
Text = "Close"
};
uiBuilder.AppendBlock(applyButton);
_engine.ShowUI(uiBuilder);
}
private void LogInformationEvent(string message)
{
_engine.GenerateInformation(message);
}
private SectionDefinition GetSectionDefinitionByName(string name)
{
var filter = SectionDefinitionExposers.Name.Equal(name);
var sectionDefinition = _domHelper.SectionDefinitions.Read(filter).FirstOrDefault();
var traceData = _domHelper.DomInstances.GetTraceDataLastCall();
if (!traceData.HasSucceeded())
{
FailPopUp($"Retrieving the SectionDefinition by name ({name}) failed with TraceData: {traceData}");
}
return sectionDefinition;
}
private void PushPaToken(DomInstance domInstance)
{
var title = GetFieldValue<string>(domInstance, OrderDetailsSectionDefinitionName, OrderIdFieldName)?.Value ?? $"Unnamed order ({domInstance.ID.Id})";
try
{
ProcessHelper.PushToken(PaProcessName, title, domInstance.ID);
}
catch (PaProcessNotFoundException)
{
// The process does not exist. It is probably not created yet. Ignore this...
_engine.GenerateInformation(
"The resource selection script for the 'Service Order' app tried to push a PA token for the process " +
$"with name '{PaProcessName}' but the process does not exist yet. Continuing execution...");
}
catch (Exception e)
{
FailPopUp($"Pushing the PA token for process {PaProcessName} failed with an exception: {e.Message}");
}
}
}
}
]]>
</Value>
<!--<Param type="debug">true</Param>-->
<Message />
<Param type="ref">C:\Skyline DataMiner\ProtocolScripts\ProcessAutomation.dll</Param>
</Exe>
</Script>
</DMSScript>