User Request where TTO start on a planned date
Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
- learning:
- Add a state on lifecycle and automatic transition on threshold
- level:
- Intermediate
- domains:
- XML, PHP, Lifecycle, Stopwatch, Automation
- min version:
- 2.1.0
In this tutorial, we want a user to be able to create a User Request in advance, so it can be put in the queue, but only work-on after the planned date. TTO and TTR would not start until the planned date is reached.
In order to reach this goal, we will customize the UserRequest class:
-
Add a new field “planned date” and display it
-
Create a new state “scheduled”,
-
Create automatic stimulus “ev_autoschedule” and associated transitions to move from “new” to “scheduled” and vice versa.
-
At creation, if a “planned date” is documented, force the transition from “new” to “scheduled” to occur.
-
Create a stopwatch with threshold to count time and switch back to “new” when planned date is reached
Adding Date & State
Step 1, 2 and 3 can be done easily based on this Tutorial tutorial.
The result is here:
- itop_design
-
<classes> <class id="UserRequest"> <fields> <!-- Add a planned date --> <field id="planned_date" xsi:type="AttributeDateTime" _delta="define"> <sql>planned_date</sql> <is_null_allowed>true</is_null_allowed> <default_value/> </field> <!-- Add a new state value to the existing status --> <field id="status" _delta="must_exist"> <values> <value id="scheduled" _delta="define">scheduled</value> </values> </field> <!-- Add a stopwatch to count the timespent in this state --> <field id="scheduledstopwatch" xsi:type="AttributeStopWatch" _delta="define"> <states> <state id="scheduled">scheduled</state> </states> <working_time/> <thresholds/> <always_load_in_tables>true</always_load_in_tables> </field> </fields> <lifecycle> <stimuli> <!-- Add a new stimulus to programatically change state --> <stimulus _delta="define" id="ev_autoschedule" xsi:type="StimulusInternal"/> </stimuli> <states> <state id="new" _delta="must_exist"> <flags> <attribute id="scheduledstopwatch" _delta="define"> <hidden/> </attribute> </flags> <transitions> <!-- Add first transition to set the UserRequest to "scheduled" --> <transition id="ev_autoschedule" _delta="define"> <stimulus>ev_autoschedule</stimulus> <target>scheduled</target> <actions/> </transition> </transitions> </state> <!-- Define the details of the "scheduled" state --> <state id="scheduled" _delta="define"> <flags> <!-- Display the stopwatch for didactic purpose --> <attribute id="scheduledstopwatch"> <read_only/> </attribute> <!-- Prevent planned date to be modified --> <attribute id="planned_date" _delta="define"> <read_only/> </attribute> </flags> <transitions> <!-- Add second transition to set the UserRequest back to "new" --> <transition id="ev_autoschedule"> <stimulus>ev_autoschedule</stimulus> <target>new</target> <actions/> </transition> </transitions> <!-- The displayed fields are the same as in "new" state --> <inherit_flags_from>new</inherit_flags_from> </state> <!-- In iTop without extensions, the rest of the states are chained to assigned --> <!-- so this read_only setting will propagate --> <state id="assigned" _delta="must_exist"> <flags> <attribute id="planned_date" _delta="define"> <read_only/> </attribute> </flags> </state> </states> </lifecycle> <!-- Display the stopwatch in the details of the UserRequest --> <presentation> <details _delta="must_exist"> <items> <!-- First column of the UserRequest display --> <item id="col:col1"> <items> <item id="fieldset:Ticket:moreinfo"> <items> <!-- This displays a stopwatch (including some sub-items) --> <!-- This is just to help you understand how a stopwatch behave --> <item id="scheduledstopwatch" _delta="define"> <rank>90</rank> </item> </items> <rank>20</rank> </item> </items> </item> </items> </details> </presentation> </class> </classes> <dictionaries> <dictionary id="EN US"> <entries> <entry id="Class:UserRequest/Attribute:status/Value:scheduled" _delta="define"> <![CDATA[Waiting for planned date]]> </entry> <!-- The below entry is never displayed as it's a programmatic stimulus --> <entry id="Class:UserRequest/Stimulus:ev_autoschedule" _delta="define"> <![CDATA[Wait for planned date]]> </entry> <entry id="Class:UserRequest/Attribute:scheduledstopwatch" _delta="define"> <![CDATA[Scheduled stopwatch]]> </entry> </entries> </dictionary> </dictionaries>
Automatic transition
This is new, we have not yet explained how we could automatically force a transition based on a condition. If there is a “planned_date” documented, then we want to move the User Request to “scheduled” state. This requires to code a few lines of PHP. There are different methods to do so, here is one. We will defined a new PHP method on the class UserRequest:
- class:UserRequest
-
protected function AfterInsert() { parent::AfterInsert(); if (!empty($this->Get('planned_date'))) { $this->ApplyStimulus('ev_autoschedule'); } }
Let's go in the details of those 3 instructions:
parent::AfterInsert();
-
This is asking the parent class to do it's own work, so here the Ticket class
-
If someone has defined an AfterInsert() method on the Ticket class, our definition on UserRequest would overwrite the method above, unless we call it.
-
This is good practice to always call the parent class, usually at the beginning.
$this→Get('attribute_code')
-
$this
represent the current object, here a UserRequest -
$this->Get('attribute_code')
allow to get the value for an attribute identified by its code -
Be cautious, if the attribute code does not exist on the object class, you'll get a Fatal Error
ApplyStimulus('stimulus_code')
-
In our case the UserRequest is created in state
new
which is the
When you define one of those overwritable methods, be aware that
it may have been done already within the standard iTop datamodel.
You will see it at setup/toolkit compilation, if you add the method
with a _delta=“define”
it will fails if it exists
already. In that case, you can do a redefine, but mirror the
existing code contained in the current version or you will break
the current behavior of iTop.
To apply a stimulus, using OnInsert() instead of AfterInsert() leads to a mysql error:
Error: Could not connect to the DB server: host = localhost, user = root, mysql_errno = 1040, mysql_error = Too many connections.
Revert to New when date reached
In order to automatically switch back to the state “new” when the planned date is reached, we will use more possibilities offered by the Stopwatch attribute. On top of counting time when the object is one of the defined status, we will define a goal and when the goal is reached, it will automatically execute a method.
Replace the above field definition for the stopwatch with this one:
- itop_design / classes / class@UserRequest / fields
-
<field id="scheduledstopwatch" xsi:type="AttributeStopWatch" _delta="define"> <states> <state id="scheduled">scheduled</state> </states> <!-- The goal must be a class which implements iMetricComputer --> <!-- we will define our own class --> <goal>ScheduledStart</goal> <thresholds> <!-- The id must be numeric. It is a percentage of goal --> <!-- Here we just want to act when the goal is reached, so 100% --> <threshold id="100"> <actions> <action> <!-- Name of a method defined on that class, to execute --> <!-- this method trigger the transition --> <verb>ApplyStimulus</verb> <params> <!-- A stimulus is a string --> <param xsi:type="string">ev_autoschedule</param> </params> </action> </actions> </threshold> </thresholds> <!-- Why to flag a stopwatch to true, is it required ??? --> <always_load_in_tables>true</always_load_in_tables> </field>
Note that we have not defined any working_time, as the default which is 24h*365d is what we need for this usage of a stopwatch.
iMetricComputer
:-
GetDescription()
return a text description. -
ComputeMetric($oObject)
return a number of “working” seconds.
We need to define in PHP, our goal class. The goal is computed
when the stopwatch is started, so when we enter “scheduled” state.
Our goal is the planned date, but the stopwatch works with a number
of seconds, so a delay. Within the ComputeMetric
method we will calculate the delay between now and the
planned_date. If the planned_date is in the past, we define 0s as
the goal.
- main.my-extension.php
-
class ScheduledStart implements iMetricComputer { public static function GetDescription() { return "Time to wait before reopening a scheduled ticket"; } public function ComputeMetric($oObject) { $iDelay = null; /* returning 'null' means 'no goal' */ // If there is a planned_date attribute and it has a non-empty value if (MetaModel::IsValidAttCode(get_class($oObject), 'planned_date') && (!empty($oObject->Get('planned_date'))) { $oStartDate = new DateTime(); $oEndDate = new DateTime($oObject->Get('planned_date')); // Calculate the delay between now and the planned_date $iDelay = $oEndDate->format('U') - $oStartDate->format('U'); // If the planned_date is in the past, we define 0s as the goal $iDelay = ($iDelay < 0) ? 0 : $iDelay; } return $iDelay; } }
The easiest is to put this code into the file
main.my-extension.php
of your extension.
This tutorial has some limitations which are explored here: Planned User Request (advanced)