Calculated field on 1:n
Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
- learning:
- Update an object based on related objects
- level:
- Advanced
- domains:
- PHP, Automation
- min version:
- 3.2.0
Assuming you want to define a “calculated” field on Class A, which would depend on data defined on class(es) B object(s), something more complex than just an ExternalField. For examples:
-
On a Ticket, you want the sum time spent on all of its WorkOrders (1:n)
-
On a Ticket, you want the count of manually added CIs (n:n)
-
On FunctionalCI, you want a Location field, which would be simply the location for PhysicalDevice, and a computed location on Logical Device, based on the location of the Physical device on which the Logical is running.
-
…
You want to be able to search on that
“calculated” field.
You want to be able to run audit against that
“calculated” field.
Simple Strategy
Here is the generic strategy to implement such “calculated” field which aggregates any field of a one-to-many relationship or just count a many-to-many relationship
-
To be able to query on it, we must store it in database, so make it a persistent field.
-
In order to ensure that it is always accurate, you need to identify when it must be recomputed and do it. For this we will define/overwrite those functions:
For more complex computed field.
On Class A
-
Write a method to calculate the “calculated” field value based on other fields / related objects. In some case it can directly be used as a callback of EVENT_DB_LINKS_CHANGED
-
Callback method on EVENT_DB_SET_INITIAL_ATTRIBUTE_FLAGS: to specify that the “calculated” field is hidden in creation form
-
Callback method on EVENT_DB_SET_ATTRIBUTE_FLAGS: to specify that the “calculated” field is read-only on modification
-
Callback of EVENT_DB_LINKS_CHANGED which compute the calculated field and set it on the object
-
Force the
with_php_computation
XML flag of the LinkedSet or LinkedSetIndirect field to “true”
Nothing to do on remote class, as we used to do before we
add events and the with_php_computation
XML
flag
Use Case: Sum 1:n
In this use case, we want to Sum on a Ticket the timespent on all WorkOrders of this Ticket.
-
First we need to add a
time_spent
integer attribute on WorkOrder and Ticket classes -
In order to ensure that
Ticket::time_spent
is always accurate, we need to identify when it must be recomputed:-
When the Ticket is updated
-
When a WorkOrder is created → we need to recompute its Ticket
-
When a WorkOrder is updated → we need to recompute its former and its new Ticket
-
When a WorkOrder is deleted → we need to recompute its former Ticket
-
-
Then define/overwrite those functions:
On Class Ticket
Event Listeners
-
EVENT_DB_BEFORE_WRITE ⇒ BeforeWrite()
-
EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS ⇒ HideTimeSpent()
-
EVENT_DB_SET_ATTRIBUTES_FLAGS ⇒ MakeTimeSpentReadOnly()
-
EVENT_DB_LINKS_CHANGED ⇒ ComputeTimeSpent()
Activated DB_LINKS_CHANGED
- itop_design / classes
-
<class id="Ticket" _delta="must_exist"> <fields> <field id="workorders_list"> <with_php_computation _delta="force">true</with_php_computation> </field> </fields> </class>
Callbacks methods
-
ComputeTimeSpent()
: calculate the “time_spent” field value based on “time_spent” of WorOrders. -
BeforeWrite()
: Workorders cannot be modified at the same time as a Ticket in the default datamodel, but if this is enable with theedit mode
then this function is needed. -
HideTimeSpent()
: to specify that the “calculated” field is hidden on Ticket creation form -
MakeTimeSpentReadOnly()
: to specify that the “calculated” field is read-only on Ticket modification form
- class::Ticket
-
protected function ComputeTimeSpent(?Combodo\iTop\Service\Events\EventData $oEventData) { $iSum = 0; $oWorkOrderSet = $this->Get('workorders_list'); while($oWorkOrder = $oWorkOrderSet->Fetch()) { $iSum += $oWorkOrder->Get('time_spent'); } $this->Set('time_spent', $iSum); } public function BeforeWrite(Combodo\iTop\Service\Events\EventData $oEventData) { $aChanges = $this->ListChanges(); if (array_key_exists('workorders_list', $aChanges)) { $this->ComputeTimeSpent(null); } } public function HideTimeSpent(Combodo\iTop\Service\Events\EventData $oEventData) { $this->ForceInitialAttributeFlags('time_spent', OPT_ATT_HIDDEN ); } public function MakeTimeSpentReadOnly(Combodo\iTop\Service\Events\EventData $oEventData) { $this->ForceAttributeFlags('time_spent', OPT_ATT_READONLY); }