CPMs: Custom Process Management

The Problematic Workflow

Custom Process Models are perhaps some of the most useful features released for developers since custom objects in the Nov '10 release. They allow custom PHP code to be triggered whenever custom and select standard objects in the system are either created, updated or destroyed. This functionality was fully released in the Nov '12 version.

This is liberating! We now have the ability to run server 'events' on both standard and custom objects. But when you start working with the Process Designer (under Site Configuration in the console) you will notice that the UI and deployment process is extremely clunky. Here's how the Sisyphean CPM development cycle works using the console Process Designer:

Creating a New CPM

  1. Click the New button in the ribbon
  2. Name your CPM
  3. Write your code and tests (really the only important part of the whole process, right?)
  4. Upload your PHP file via the Process Designer UI (only 1 file allowed, mind you!)
  5. Click the save button in the top left
  6. Click the test button in the middle of the screen
  7. Click the OK button after your test methods execute successfully (if they don't, fix your code and go back to the "Upload your PHP file..." step)
  8. Click the Yes button to ensure that you are absolutely certain that your tests executed correctly... are you sure? really?
  9. Drill into the objects in the left pane, find the object you want to attach your process to, and select it
  10. In the right pane, select your process from the menus for the object actions which are relevant
  11. Click the save button in the top left again
  12. Finally, click the deploy button

"Phew! Oh, wait, I forgot something..." Update your code and start back at the "Upload your PHP file..." step. Yes, clunky. If you have very simple logic, this is not an issue, just more of an annoyance, but most of us will want to have a more efficient development process. No matter what, you will have to do this manual process at least once, but this article describes how to setup a standard CP development cycle to upload code via WebDAV that is directly run in your CPM.

The Better (still not perfect) Workflow

If you don't have any experience with CPMs, I'd take a look at the CPMs 101 post before digging into this. What we're going to do is to leverage the Customer Portal WebDAV and deployment system to house our PHP code, which will be called by the CPM framework.

Here's a skeleton incident CPM that does absolutely nothing. We'll start with this and add functionality to it:

<?php /** * CPMObjectEventHandler: Incident * Package: RN * Objects: Incident * Actions: Create, Update, Destroy * Version: 1.2 */ use \RightNow\CPM\v1 as RNCPM; class Incident implements RNCPM\ObjectEventHandler { public static function apply($runMode, $action, $incident, $cycles) { } } class Incident_TestHarness implements RNCPM\ObjectEventHandler_TestHarness { public static function setup() { } public static function fetchObject($action, $objectType) { } public static function validate($action, $object) { return true; } public static function cleanup() { } }

You'll notice the CPM class name is completely generic. I do this for a few reasons:

  1. I like to build a flexible structure that can route logic based on the action (create, update, destroy) within code, and is not reliant on the settings in the CPM UI. I apply the same CPM handler in the UI for object create, update and destroy.
  2. Only a single CPM can be attached to an object type. To add new business logic you need to modify the existing CPM handler, so keep the naming conventions generic from the very beginning.

Then we're going to upload a skeleton PHP class via WebDAV, to the CP libraries folder. In CP3, this is under cp/customer/development/libraries. I typically create cpm/v1 sub-directories to organize my CPM handlers with a version number to make updates easier and less susceptible to side-effects. This file will contain our real CPM business logic:

<?php /** * Skeleton incident cpm handler. */ namespace Custom\Libraries\CPM\v1; class IncidentHandler { static function HandleIncident($runMode, $action, $incident, $cycle) { printf("Testing:\nRun mode: %s\nAction: %s\nIncident ID: %d\nCycle: %d\n\n", Labels::RunMode($runMode), Labels::Action($action), $incident->ID, $cycle); } }

Next we need to modify our CPM to include this library, and use a couple of constants to handle switching between CP development and production mode (if you wanted to get fancy, you could also add staging mode to this, but I find dev and prod are sufficient). Add this after the 'use' statement:

use \Custom\Libraries\CPM\v1 as CPMHandler; /** * When set to 'true', the CPM will run the CP handler library in development * mode. This should be set to 'false' once the handler logic has been tested * and the CP library has been deployed. */ const DEV_MODE = true; define('APPPATH', DEV_MODE ? __DIR__ . "/scripts/cp/customer/development/" : __DIR__ . "/scripts/cp/generated/production/optimized/"); require_once APPPATH . "libraries/cpm/v1/IncidentHandler.php";

And add this to the apply() method, to invoke our library handler:

CPMHandler\IncidentHandler::HandleIncident($runMode, $action, $incident, $cycles);

At this point your development CP library method HandleIncident() will be run anytime an incident is created, updated, or destroyed! Updating the CPM logic is simply a WebDAV push away. Once your logic has been tested, you simply need to deploy your library in CP, and set the DEV_MODE constant to 'false' and your CPM will run the production optimized version of the library.

I've attached a zip of these file skeletons to get you started, with some additional functionality:

  • An example class that appends a string to the subject of an incident on Create or Update
  • A Labels helper class for code-complete and string mapping with the run mode and action CPM constants for easy debugging
Incident Example CPM

Comments

Hey Ben,

nice setup! However I would say that with the new addition of calling CPM's directly from business rules this setup would not be the best choice, as it does not allow to pick specific actions from the business rules..?

Bastiaan

Hey Bastiaan, thanks for pointing this out. When I have a chance to work more intimately with the new business rule functionality, I'll be sure to update this post. In the meantime, any suggestions are welcome!

Great idea for offloading a lot of the work to a library file, however I'm running into some trouble when using my goto debugging of logMessage. I can't seem to get this function to expose to the library. Ive tried doing use Rightnow\Utils\Framework and calling it via Framework::logMessage, but its telling me logMessage not found. Am I running into some sort of namespacing issue in the library? I haven't used this part of the CI framework too much, so I'm not totally sure how to include external libraries.

Placing the library file within the customer portal directory structure simply provides the ability to update via WebDAV and leverage CP's deployment functionality. Customer Portal, however, is not bootstrapped, so none of the standard CP libraries are available for use.

I'd recommend logging to a CBO object, instead, or even a flat-file. (It is possible to manually include some CP utility functionality, but you'd have to resolve any dependencies on the core framework, which would cause a lot of unnecessary overhead.)

I have written a CPM script that runs on Incident update and checks for the value of a custom field. If the value is 1, it updates the incident. Everything works fine except the fact that whenever I update and save the incident, the incident thread gets updated 5 times.

I have added the below line of code already.

if ($cycle !== 0) return;

But, this does not help. I have also noticed that, if at the end of the code that updates the incident, I make the custom field value as 0, it does not update the incident multiple times. But, this does not looks logical and moreover, I do not want to make the value of the custom field again to zero.

Can anyone please suggest some solution here.

Please see my response and question on your discussion post.

For those of you still on CP2, you'll need to set a different reference path for your APPPATH constant

Use this instead:


define('APPPATH',
        DEV_MODE ?
                __DIR__ . "/scripts/euf/application/development/source/" :
                __DIR__ . "/scripts/euf/application/production/optimized/");
                

How to call CP controller function from CPM?

EX: www.domainaname.com/cc/controllername/ref/1231212

How to call above page from CPM, i'm using curl or file_get_content, it showing error.

$curl_result = json_decode(curl_get_contents($url)),true);

Thanks

I do not recommend using curl in this case; the only time curl should be used is when you need to call a web service in a separate system. Encapsulate your controller logic in a CP library class, then include this library file from within your CPM and run it directly.

Dear Guys,

We are trying to creating and updating custom object based on incident status, but custom object not creating. but fetch/update is working fine.

$emp_query = RNCPHP\ROQL::queryObject("SELECT CO.Employee FROM CO.Employee where Incident=$i_id")->next()->next();

// Check if emp already exist or not
if($emp_query==null) $Employee_Object = new RNCPHP\CO\Employee();
else
$Employee_Object = RNCPHP\CO\Employee::fetch($emp_query->ID);

Please can you help me.

Thanks

Hi Ben,

I'm trying read file attachments of incident. The getAdminURL() function is working only in CP files and I'm trying to call controller from CPM(under library) to get the URL link.
I tried using CURL but the its giving 404 error.

Please help to solve this issue

Zircon - This is a contributing Drupal Theme
Design by WeebPal.