B-3-17HC18P

Using PAPI: Part 7 - Updating a Behavior

Blog Post created by B-3-17HC18P Employee on Jun 25, 2015

Welcome back to "Using PAPI", an ongoing blog series where we build a Node/Express/Angular app that uses the Property Manager API (PAPI). You'll want to catch up on our previous installments if you haven't done so:

 

 

In part 6 we created a view that displayed behaviors on a particular property. Now, in this final part of the Using PAPI blog series, we're going to update a behavior with a PUT request made via a form.

 

If you'd like to take a look at the completed code first, you can find it on Github.

 

Consult the PAPI documentation

 

For the purposes of this tutorial we're going to provide a button to update the caching behavior on a property. In the Behaviors section of the PAPI documentation you find a section called "caching" which lays out the set of options that can be passed to the API. According to that documentation, we know there is a behavior option that can be set to one of the following:

 

  • no-store - clears the cache and serves from origin
  • bypass-cache - retains cache but serves from origin
  • max-age - honors the origin's "max-age" header (same as "Cache" option in the Luna Property Manager)
  • cc - honors the origin's "cc" header
  • expires - honors the origin's "expires" header
  • both - which honors cc and expires

 

Update the proxy server

 

Now that we know the values we want to set, we need a way to tell PAPI we want to update this. We do this by referring to the documentation and seeing that we need to make a PUT request to the same endpoint we used to fetch our rules in part 6 of the tutorial. This means we're going to have to update the local API proxy server that we built in part 3, since the one we built only makes GET requests.

 

In routes/api.js, which is the file that handles all of our API proxying, we need to define a route that handles PUT requests:

 

/* handle a PUT request to papi/v0/whatever-we-want */
router.put('/', function(req, res, next) {
  var eg = req.app.get('eg');
  // call the endpoint (e.g., "papi/v0/groups/")
  eg.auth({
    'path': req.originalUrl, // the endpoint we got from the documentation
    'method': 'PUT', // our method type, also from the docs
    'headers': {
      'Content-Type': 'application/vnd.akamai.papirules.latest+json'
    }, // we need to include the Content-Type header noted in the docs
    'body': JSON.stringify(req.body) // pass along the body of our request
  });
  // actually kick off our request to the API server
  eg.send(function(data, response) {
    res.json({
      status: response.statusCode,
      data: JSON.parse(data)
    });
  });
});








 

This is almost the same as our GET route, with a couple of notable exceptions. First, we provide a "Content-Type" header of "application/vnd.akamai.papirules.latest+json" -- we learned this information from the reference documentation. Specifically when we click on the "PUT" verb in the Property Version Rules section, the console slides out from the right and any required headers are listed there:

 

put.gif

 

We also include a "body" parameter when we make our EdgeGrid request. In this case, we're grabbing req.body from the router itself (which just passes along the body of the request made to our proxy server) and running JSON.stringify on it since the API expects a JSON string rather than a literal JavaScript object.

 

Lastly, we make sure our API method is a PUT instead of a GET when we make our EdgeGrid request.

 

You can look at the diff for this change on Github.

 

Create a flexible way to specify options

 

Instead of hard-coding a form just for the caching behavior we want to change, let's make a flexible way to specify the various options we have available in PAPI on different values. First we'll define an object that contains our form information. We're going to assume dropdown SELECT prompts for the purposes of this blog post, but you could make the object more complex to support other types if you like.

 

First, in public/js/app.js, we're going to modify our PropertyRulesController callback function to declare the following object right at the top:

 

    $scope.options = {
      caching: {
        behavior: [
          'no-store',
          'bypass-cache'
        ]
      },
      origin: {
        'http_port': [
          '80',
          '8080'
        ]
      }
    };






 

The $scope.options object contains sub-objects whose keys ("caching" and "origin") correspond to the names of different behaviors in the $scope.propertyRules.behaviors collection. Each sub-object contains arrays that are named for the value in $scope.propertyRules.behaviors.options that we want our form to modify.

 

The next thing we want to do is wait for $scope.propertyRules to get populated with its real values and then add a selectOptions to each object in the $scope.propertyRules.behaviors collection corresponding to the correct value from the $scope.options object. We're doing this so that we can dynamically change the value of $scope.propertyRules by using ngOptions to bind our dropdown options to $scope.propertyRules.behaviors and map everything correctly. This all sounds a little complicated but in practice it's not that difficult!

 

Inside our PropertyRules.query callback, right after we set $scope.propertyRules, we add some code that iterates through each subobject of $scope.options and then assigns it to a selectOptions subobject on the appropriate member of the $scope.propertyRules.behaviors collection.

 

      $scope.propertyRules = res.data.rules;
      for (key in $scope.options) {
        // find the index of the subobject in the propertyRules collection matching our key
        var index = $scope.propertyRules.behaviors.map(function(el) { return el.name; }).indexOf(key);
        // set a new selectOptions object in that subobject containing our new values
        $scope.propertyRules.behaviors[index].selectOptions = $scope.options[key];
      }





 

Next we add the following SELECT element to the default ng-switch behavior in our propertyRules template inside of views/index.ejs:

 

                  <span ng-switch-default>
                    <span><strong>{{key}}</strong>: {{val}}</span>
                    <select name="{{key}}" ng-if="behavior.selectOptions && behavior.selectOptions[key]" ng-model="behavior.options[key]" ng-options="v for v in behavior.selectOptions[key]">
                    </select>
                  </span>




 

What we're doing here is using ng-if to say: if there is an available set of options defined for this particular behavior, then render this SELECT element. Then we define the model we're binding to -- we're getting our dropdown data from the selectOptions object, but we are binding it to the actual options object that we'll eventually send along in our PUT request, mapped to the particular key in options (in our case this key would be "http_port" or "behavior" as we defined it above). This will cause something like this to render in the app when you're viewing individual property details:

 

Screen Shot 2015-06-25 at 8.08.33 AM.png

 

When you make changes to the dropdown box, the actual value will update in the rendered list.

 

You can view the diff for these changes here.

 

Make a PUT request from Angular

 

At this point, making our PUT request is actually the easy part. First we define a PUT verb for ngResource in our PropertyRules factory declaration:

 

  // Same as the Properties factory above but for rules
  .factory('PropertyRules', ['$resource', function($resource){
    return $resource('/papi/v0/properties/:propertyId/versions/:propertyVersion/rules', null, {
      'query': { method:'GET' },
      'put': { method:'PUT' }
    });
  }])


 

The only new part here is adding that "put" method.

 

Next we add a $scope.save() function definition right after we defined $scope.typeOf() in our PropertyRulesController:

 

    $scope.save = function() {
      // create a temp object where we store our modified property rules,
      // but then delete all instances of selectOptions and uuid since that will cause
      // PAPI to throw an error due to receiving an unexpected object
      var payload = $scope.propertyRules;
      delete $scope.propertyRules.uuid;
      for (var i=0; i < $scope.propertyRules.behaviors.length; i++) {
        delete $scope.propertyRules.behaviors[i].selectOptions;
        delete $scope.propertyRules.behaviors[i].uuid;
      }
      console.log(payload);
      PropertyRules.put({ groupId: group, contractId: contract, propertyId: propertyId, propertyVersion: propertyVersion }, {
        rules: payload
      });
    }


 

We're creating a temporary payload object since we need to modify $scope.propertyRules slightly before sending it. We get rid of the uuid fields that Angular adds, and we also remove the selectOptions fields that we added. (If we didn't do this, the API would return an error 400 and complain about invalid JSON structure.)

 

And last, we add a button to kick off the save function. We do this in views/index.ejs in our propertyRules template:

 

    <script type="text/ng-template" id="/propertyRules.html">
    <h1>Behaviors</h1>
      <button ng-click="save()">Save Changes</button>


 

And that's that! Here you can see it in action: we look at the caching behavior in Luna Property Manager, switch to our app, change the behavior, press select, then switch back and reload to see the new result:

 

whoa2.gif

 

You can view the diff for these changes here.

 

I hope you enjoyed all 7 parts of this tutorial, and I hope this series helped you on the path to writing your own applications using the Property Manager API! As always, you can ask any API related questions you have in the Developer Community and someone from Developer Relations will get back to you soon. Thanks for reading!

Outcomes