:: Version 2.7.0 ::

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:

  1. Add a new field “planned date” and display it
  2. Create a new state “scheduled”,
  3. Create automatic stimulus “ev_autoschedule” and associated transitions to move from “new” to “scheduled” and vice versa.
  4. At creation, if a “planned date” is documented, force the transition from “new” to “scheduled” to occur.
  5. 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.

a goal is a class that implements the interface 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)

2_7_0/customization/add-activation-delay.txt · Last modified: 2020/04/15 15:23 (external edit)
Back to top
Contact us