Pending Review Cleanup

This section contains an example script that cleans up pending changelists which are no longer needed. See the review cleanup options for how this can be done automatically when a review is committed.

For pending changelists which were present before this option was available, or for reviews which have been contributed to by multiple authors and so require super user access to tidy up, there is an API which allows the super user to bulk remove such changelists.

The script demonstrates how this API could be used. It isn’t meant as a complete solution, just a guide to demonstrate what is possible.

The code

<?php
/**
 * Perforce Swarm
 *
 * @copyright   2017 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
/**
 * This example script can be used to clean up (delete) Perforce Server pending changelists automatically when run
 * as a super user. It is able to query for reviews based on parameters to establish which changelists are eligible
 * for clean up. In this way, it can be tailored to run against reviews of a user's choice.

 * Requirements for this script:
 *   - MUST be a super user
 *   - MUST populate the parameters below
 *   - MUST be using Swarm 2017.1 or later
 *
 * Usage of script
 *   php superUserReviewCleanUp.php max=10 notUpdatedSince=2017-04-01 state=approved
 *   php superUserReviewCleanUp.php max=10 author=bruno state=approved
 *
 * Each of the parameters that you can use are documented at the following URL:
 * https://www.perforce.com/perforce/doc.current/manuals/swarm/api.endpoints.html#api.endpoints.Reviews.getReviews
 *
 *
 * This returns a JSON object that contains four main objects.
 *
 * {
 *   "error": "",
 *   "help": "",
 *   "results": "[]",
 *   "search_criteria": {
 *     "fields": "id",
 *     "max": "10",
 *     "notUpdatedSince": "2017-04-01"
 *   }
 * }
 *
 * Referencing the example above:
 *
 * The error section will contain any errors that have been encountered trying to execute the script.
 *
 * The help section will populated if you have run the command php superUserReviewCleanUp.php help
 *
 * The search_criteria section indicates which parameters have been used to fetch the reviews list.
 *
 * The results section returns a JSON object of each of the reviews it has processed, which may
 * include actions that were incomplete.
 *
 * Below is an example of results being processed:
 * "814": {
 *    "complete": [],
 *    "incomplete": {
 *       "814": {
 *           "813": [
 *               "0": "Command failed: No shelved files in changelist to delete.",
 *           ]
 *       }
 *    }
 * },
 * "818": {
 *    "complete": [],
 *    "incomplete": []
 * }
 * "820": {
 *    "complete": [],
 *    "incomplete": {
 *       "820": {
 *           "821": [
 *              "0": "Command failed: No shelved files in changelist to delete.",
 *              "1": "Command failed: Usage: fix [ -d ] [ -s status ] -c changelist# jobName ...\nMissing/wrong
 *                    number of arguments."
 *           ]
 *       }
 *    }
 * },
 *
 * Some reviews may have no actions or incomplete actions. Incomplete actions indicate that additional work is required
 * and the review could not be entirely cleaned up. In the example above, the first message indicates a changelist was
 * not found. This could be because the end user has already deleted it.
 *
 * An error with the fix command can indicate that the pending changelist doesn't have any jobs linked or that the jobs
 * have already been removed.
 *
 * NOTES:
 * To make the the output print nicely, you can use the python command like this:
 *
 * php superUserReviewCleanUp.php max=10 notUpdatedSince=2018-04-01 state=approved | python -m json.tool
 *
 */


/* **************************************************** */
/* These values MUST be set before running the script.  */
/* **************************************************** */

// URL to access swarm. Must not include trailing slash.
$swarmURL = 'http://my.swarm.com';
// Username of super user
$username = '';
// Ticket for user, can be created by running "p4 -u $username login -pa"
$ticket = '';

/* **************************************************** */


// If the super user wants to reopen the files of the end user.
$reopen = true;
// Prebuild the the return message helper array.
$help = array( "help" => "", "error" => "", "results" => "", "search_criteria" => "");
// @codingStandardsIgnoreEnd
/**
 * function that make the GET or POST requests
 *
 * @param $url         Url in which we want to make our request to
 * @param bool $fetch  Set to true for a GET request, otherwise will do a POST.
 * @param array $args  These are the parameter that we pass the the GET or POST request to filter the reviews
 * @return JSON        We return a JSON object back to be worked on.
 */
function request($url, $fetch = false, $args = array())
{
    // Fetch the settings to allow this function to access them.
    global $username, $ticket, $reopen, $help;
    $curl = '';

    // If GET request fetch should be true and args shouldn't be empty
    if ($fetch === true) {
        // If is args is empty just give the url, otherwise build a http query with args elements
        $curl = empty($args) ? curl_init($url) : curl_init($url."?".http_build_query($args));
    } else {
        // Assume fetch is false and build a POST request.
        $curl = curl_init($url."/".$args['id'].'/cleanup');
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query(array('reopened'=>$reopen)));
        curl_setopt($curl, CURLOPT_POST, count($reopen));
    }
    curl_setopt($curl, CURLOPT_USERPWD, "$username:$ticket");
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);

    $result = curl_exec($curl);

    // Catch the error code in case Ticket expired or url doesn't return.
    $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if ($statusCode != 200) {
        $help['error'] = array("HTTP code" => $statusCode);
    }

    curl_close($curl);
    return json_decode($result, true);
}

/**
 * Fetch a list of Reviews based on parameters
 *
 * @param $elements  Each of the args passed from command line are treated as elements
* @return JSON      We return a JSON object back to be worked on.
 */
function fetchReviews($elements)
{
    global $swarmURL, $help;
    $parameters = array();

    if ($elements !== null) {
        // Loop though each of the args we want to fetch reviews based on
        foreach ($elements as $key => $value) {
            // Break each of the element into key and value to allow use to build a array to pass to url
            $brokenDown = explode("=", $value);
            if ($key !== 0 && sizeof($brokenDown) === 2) {
                $parameters[$brokenDown[0]] = $brokenDown[1];
            }
        }
        // We only require the field id to limit the amount of data.
        $parameters['fields'] = 'id';
        // Helpful for debugging which parameters we have passed in the queue.
        $help['search_criteria'] = $parameters;
    }
    // Now make the request to the Swarm server with your field options.
    $result = request(
        "$swarmURL/api/v6/reviews",
        true,
        $parameters
    );

    // Return the JSON object back to be worked on.
    return $result;
}

/**
 * Loop though each of the Reviews passed in and run clean up for them.
 *
 * @param $reviews  JSON object of all the reviews we want to run cleanup on
 * $reviews => array(
 *      'reviews' => array(
 *          0 => array (
 *                  'id' => 134,
 *          ),
 *          1 => array (
 *                  'id' => 136,
 *          ),
 *          2 => array (
 *                  'id' => 143,
 *          ),
 *          3 => array (
 *                  'id' => 158,
 *          )
 *      )
 * )
 *
 * @return $array   return the array of work that has been carried out.
 */
function runCleanUp($reviews)
{
    global $swarmURL;
    $results = array();
    // Check if there is an reviews element of the array
    if (isset($reviews['reviews'])) {
        foreach ($reviews['reviews'] as $review) {
            // Now make the request to the Swarm for each review.
            $results[$review['id']] = request(
                "$swarmURL/api/v6/reviews",
                false,
                $review
            );
        }
    }
    return $results;
}

/**
 * The help function in case a user doesn't set the basic settings.
 *
 * @param $help    Pass the help array from main script to append the helpful message.
 * @return $array  Return the array that will be presented to the users.
 */
function helpMessage($help)
{
    $help["help"]      = array( "1" => "", "2" => "", "3" => "" );
    $help["help"]["1"] = "Please ensure you have set the Username, Ticket and Swarm URL before using this script";
    $help["help"]["2"] = "Running the script can be done by using any of the standard Swarm API fields for reviews";
    $help["help"]["3"] = "Visit https://www.perforce.com/perforce/doc.current/manuals/swarm/index.html";
    return $help;
}

// check the first argument is not help.
$helpSet = isset($argv[1]) && $argv[1] == "help" ? "help" : null;

// Check if the user has given help as a command to this script.
if (isset($argv[1]) && $helpSet == "help") {
    // Set the $help array with the help message.
    $help = helpMessage($help);
}

// Check if the basic user ticket and swarmurl are set.
if (!empty($username) && !empty($ticket) && !empty($swarmURL) && $helpSet != "help") {
    try {
        $help["results"] = runCleanUp(fetchReviews($argv));
    } catch (Exception $e) {
        $help = helpMessage($help);
    }
} else {
    $message    = "Please ensure you have set the below before using this script";
    $errorArray = array("message" => $message, "parameter" => "");

    $missingParameter = array();

    // Now check if the basic settings are empty and show the end user.
    empty($username) ? $missingParameter[] = "Username":'';
    empty($ticket) ? $missingParameter[] = "Ticket":'';
    empty($swarmURL) ? $missingParameter[] = "SwarmURL":'';

    $errorArray['parameter'] = $missingParameter;

    $help["error"] = $errorArray ;
}
// Output the end result of the what the script does.
echo json_encode($help, JSON_FORCE_OBJECT);
// @codingStandardsIgnoreEnd

Executing the script

The example is written in PHP, and demonstrates how to make use of the APIs which remove unneeded pending changelists. It must be run as a super user.

For a full set of instructions on how to use the example script, see the comments in the script itself.

Abbreviated instructions:

  1. Set the value of the $swarmURL, $username and $ticket variables.
  2. Run the script by using a command similar to the following:
$ php pendingReviewCleanUp.php max=10 author=bruno state=approved