Skip to content

lazyProgrammer

Deva Kumar edited this page Jul 20, 2020 · 13 revisions

Lazy Programmer's Guide to Developing SAS Viya Applications

Philipp Lenssen writes in an article titled "Why Good Programmers Are Lazy and Dumb"

Lazy because only lazy programmers will want to write the kind of tools that might replace them in the end. Lazy, because only a lazy programmer will avoid writing monotonous, repetitive code -- thus avoiding redundancy, the enemy of software maintenance and flexible refactoring. Mostly, the tools and processes that come out of this endeavor fired by laziness will speed up the production. This makes a lazy programmer a smart and efficient programmer.

From a SAS Viya Application developer's perspective

One of the great strengths of SAS Viya is the large collection of REST APIs that allow customers to integrate SAS Viya capabilities into their applications. Using these APIs, the user's applications access the full complement of SAS Viya capabilities - data preparation, analysis and reporting.

Just about every SAS Viya application will call a few key services to do the heavy lifting on the Viya server. The figure below shows these typical patterns.

Common Scenarios

How many times does one have to write the same code to call different REST APIs when those are all similar, but for the payload? How many times does one to have to write code to "reduce" the JSON response to be useful in presentations?

You too can become a smart lazy programmer by using the techniques discussed in this article. Spend less time writing code that writes itself, spend more time solving real business problems and use the extra time doing things you always wanted to do.

Tools for lazy programmer

Keeping with the lazy programmer point of view I have developed two JavaScript libraries to take care of the tedious part of application development.

  1. restaf is a lightweight, UI-framework agnostic, JavaScript library designed to be easy-to-use for app developers. restaf takes advantage of the consistency of the REST APIs to reduce the responses from the server into a standard form, and it also manages the data on the client side. This reduction enables developers to interact with the server in a repeating pattern regardless of which service the application is using.

  2. restaflib is a helper library built on top of restaf to simplify the common patterns described above.

 

Code developed with these libraries run in a browser or a nodejs application. All the examples shown in this article run in either scenario.

Using the libraries

Web Applications

Include the following two script tags.

<script src="https://unpkg.com/@sassoftware/restaf/dist/restaf.min.js"></script>

<script src="https://unpkg.com/@sassoftware/restaflib/dist/restaflib.min.js"></script>

Two globals restaf and restaflib will be available for use in your script tags.

Node.js application

Install restaf and restaflib using the following command

npm install @sassoftware/restaf @sassoftware/restaflib

Program initialization

A typical initialization code(for both web and nodejs applications) will look like this:

  let store = restaf.initStore();
  store.logon(logonPayload)
    .then (() =< {
        ...your code...
    })
    .catch(err => {
        ...handle errors...
    })

Please refer to the authentication page of the restaf repository for details on how the logon method works.

Contents

For fast access to specific sections use these links

The rest of article explains how one can program for the common scenarios shown in the figure above.

 


Run Traditional SAS Programs on server


A traditional SAS program consists of PROC statements, data step, and macros. In SAS Viya this executes using the compute service.

The compute service API covers many different uses. The simplest and most common use cases are run a SAS job and view the ODS output, log, listing, and browse the generated tables.

Setup: computeSetup

This method creates a compute service session.

Syntax

    let session = await restaflib.computeSetup(store, contextName);

The parameters are:

  • store - restaf store object

  • contextName - (optional) if not specified the first context in the list is used

Run: computeRun

This method executes the specified code on the server

Syntax

    let computeSummary = await restaflib.computeRun(store, session, src, macros);

The parameters are:

  • store - restaf store object

  • session - the session object obtained thru the computeSetup method

  • src - a string containing the source code to be executed

  • macros - (optional) A javascript object that is sent to the server as macros. For example, if this object is {lib: 'sasuser', data: 'mydata'} the following macros are prepended to the source code.

        %let lib=sasuser;
        %let data=mydata;
    

The result from the computeRun method is used to retrieve specific results using the following methods.

Results: computeResults

This method retrieves the ods output, log, listing, and the list of tables.

Syntax

        let result = await restaflib.computeResults(store, computeSummary,
type);

The parameters are:

  • store - the restaf store

  • computeSummary - the output from restaflib.computeRun

  • type - A string with one of the following values ods|log|listing|tables. Based on this option different results are returned.

The results based on the value of type are:

  • ods - This returns a string that has the ODS output. This html can be passed to some component for display.

  • log - This returns all the log for this particular job. The content is an array of objects that follows the media type for loglines

  • listing - This returns all the listings for this particular job with the same format as the log.

  • tables - This returns an array with the names of the tables from the job. Use this name to retrieve the actual records using the restaflib.computeFetchData method described below.

Data: computeFetchData

Use this method to page thru the records of the tables returned by the computeRun method.

Syntax

    let result = await restaflib.computeFetchData(store, computeSummary,
    tablename, direction);

The parameters are:

  • store -- the restaf store

  • computeSummary - the output from restaflib.computeRun

  • tablename - The name of the table returned (see computeResults method above).

  • direction - A string indicatilng which direction to scroll (next, prev) and get records. If not specified, the first set of records are returned. The valid values of this parameter depend on what was returned in the scrollOptions field of the result.

The result from the computeFetchData has the following schema:


   { 
    rows         : An array of data rows - [{val1, val2,...}, {val1,val2,...}] ,
    columns      : The standard schema for the columns,
    scrollOptions: An array of valid scrolling directions - first,prev,next,last
   }

Example

    async function example () {
        let computeSession = await computeSetup(store);
        /* prep input */
        let macros = {maxRows: 500};
        let src =  `ods html style=barrettsblue;
        data work.dtemp1;
            array x{10};  
            do j = 1 to &maxRows;  
            do i = 1 to 10;  
                x{i} = i * 10;  
                end;  
            output;  
            put _ALL_;  
            end;  
            run;  
            proc print;run;  
            ods html close;`;
        // Run the SAS job
        let computeSummary = await restaflib.computeRun(store, computeSession, src,  macros);

        // Get log, listing, ods and list of tables
        let log = await restaflib.computeResults(store, computeSummary, 'log');
        let list = await restaflib.computeResults(store, computeSummary, 'listing');
        let ods = await restaflib.computeResults(store, computeSummary, 'ods');
        let tables = await restaflib.computeResults(store, computeSummary, 'tables');

        // Scroll thru one of the tables
        let data = await restaflib.computeFetchData(store,computeSummary, 'DTEMP1');
        console.log(data.columns);
        console.log(data.rows);

        do {
        data = await restaflib.computeFetchData(store,computeSummary, 'DTEMP1', 'next');
        if (data !== null) {
            console.log(data.rows);
        }
        } while (data != null);

    // Delete session
        await store.apiCall(computeSession.links('delete'));
    }

 

 


Run CAS action


Setup: casSetup

This method creates a CAS Session

Syntax

    let {session} = await restaflib.casSetup(store);

Run: casActionRun

This method runs the specified action.

Syntax

let actionResult = await restaflib.casActionRun(store, session,
payload);

The arguments to this method are:

  • store - the store object for restaf

  • session - the session object from the casSetup method

  • payload - an object with the schema describe below

The payload is:

{ 
    action : name of the action - actionset.actionname, 
    data   : parameters for the selected actio
    headers: If needed
}

Example

async function example() {

let {session} = await restaflib.casSetup (store);

  let p = {

    action'builtins.echo',

    data  : {

      code'data casuser.score; x1=10;x2=20;x3=30; score1 = x1+x2+x3;run; '

    }

  };

  r = await restaflib.casActionRun(store, session, p);

  await store.apiCall(session.links('delete'));

  return 'done';

};

Processing results

The result of the action will vary based on the action. For more details, refer to the CAS Actions documentation.

Notes on table.upload action

This action requires information be passed in the header. Below is an example for uploading a cars.csv file to casuser.cars:

    let JSON_Parameters = {
        casout: {
          caslib: 'casuser' /* a valid caslib */,
          name  : 'cars' /* name of output file on cas server */
        },

        importOptions: {
          fileType: 'csv' /* type of the file being uploaded */
        }
      };

      let p = {
        headers: { 'JSON-Parameters': JSON_Parameters },
        data   : readFile(filename, fileType),  /* read file from a local csv file */
        action : 'table.upload'
      };

      let actionResult = await restaflib.casActionRun(store, session, p);
      ... process results ...

A simpler way is to use the casUpload method, described in the next section.

 

 


Upload Data to CAS


The casUpload method handles the common scenario of loading CSV  into CAS for further processing.

Setup: casSetup

This method creates a CAS Session.

Syntax

    let {session} = await restaflib.casSetup(store);

Run: casUpload

Syntax

    let r = await restaflib.casUpload(store, session, filePath, output,
    save, altSrc);

The parameters are:

  • store -- the restaf store

  • session -- the current CAS session obtained via casSetup

  • filePath -- path to the file (ex: /mydata/cars.csv)

  • output -- set to true if you want the data to be saved; the default is false

  • altSrc -- in some applications the CSV file is generated programmatically -- in that case pass the string here

The casUpload function handles other file types also -- see here for more details.

Example

    async function example(path, table) {

         let {session} = await casSetup(store);

         await restaflib.casUpload(store, session,
    './mydata/cars.csv', 'casuser.cars', true, null);

     };

 

 


Run CASL Script on Server


A common workflow may require multiple CAS actions, in sequence, with additional flow control. The CASL language is ideal for such scripting. The CASL code executes using the sccasl.runCASl action. While this action could run using the casActionRun method, the usefulness of this approach dictates a separate method. This section shows the use of caslRun method to run the CASL scripts on the server.

Run: caslRun

Syntax

    let result = restaflib.caslRun(store, session, casl, args)

The arguments to this function are:

  • store - the restaf store

  • session - the session obtained through casSetup

  • casl - A string with the CASL code to execute

  • args - The args parameter (a standard js object) is passed to the CASL program as a CASL dictionary named _args_. This is a friendly way to pass parameters to your CASL programs.

The caslRun executes the sccasl.runcasl action on the CAS server. If the status in the results is non-zero caslRun throws an error you handle in your catch block. The key elements of the response are:

  • result.log - is the log lines

  • result.disposition - the standard CAS disposition object

  • result.results - the object passed to the send_response function in the CASL program

 

Example

    async function example() {
      let {session} = await restaflib.casSetup (store);                                  
      /* Recommendation: Store the casl code in a repository */
      let casl = `                                                                       
              print 'input values';
              print _args_;
               action datastep.runcode/ single='YES' code = 'data casuser.a; x=1; run;';
               action table.fetch r=r1/
                  table= { caslib= 'casuser', name= 'a' } ;
                  run;
                  action datastep.runcode/ single='YES' code = 'data casuser.b; y=1; run;';
                action table.fetch r=r2/
                  table= { caslib= 'casuser', name= 'b' } ;
                  run;
               c = {a=10, b=20}; /* just to show that anything can be returned */
               send_response({a=r1, b=r2, c=c});
            `;
      let args   = {a: "this is arguments", b: "more data"};

      let result = await restaflib.caslRun (store, session, casl, args);
      console.log(JSON.stringify(result, null,4));
      await store.apiCall(session.links('delete'));
     }

 

 


Score with MAS


The Micro Analytic Score (MAS) service runs real-time scoring for many models and a large number of users. The models to be scored are published to the server. 

Setup: masSetup

This method sets up the necessary information to score with MAS.

Syntax

        let masControl = await restaflib.masSetup(store, models);

The arguments to this method are:

  • store - restaf store

  • models - an array of model names you want to use for scoring

Describe: masDescribe

Use this method to get information on the input to the scoring model.

Syntax

    let input = masDescribe(masControl, modelName, step);

The parameters to this method are:

  • masControl - the object returned from masSetup

  • modelName -- the name of the model

  • step - the step you are interested in (defaults to score)

Run: masRun

This is the method you will call to score each scenario

Syntax

    let result = await restaflib.masRun(store, masControl,
    modelName,scenario, step);

The parameters to this method are:

  • store -- the restaf store

  • masControl - the object from casSetup

  • modelName - the name of the model to use for scoring; has to have been specified in the models array to casSetup

  • scenario - a JS object with the input values (see example below)

  • step -- (optional) If not specified, the first 'score' in the model will be used for scoring.

Example

async function example () {
  let models = ['sdktgo_iris`];`
  let masControl = await restaflib.masSetup(store, models);

  let scenario = {
     petallengthcm: 1.5,
     petalwidthcm : 0.2,
     sepallengthcm: 5,
     sepalwidthcm : 3
   };
  let result = await ``restaflib.masRun`` (store, ``masControl``, models[0], scenario);
  console.log(result);
};

The sample output is shown below

A sample result is:

{ 
  I_Species``               : 'Iris-setosa',
  P_SpeciesIris_setosa      : 1,
  P_SpeciesIris_versicolor  : 0,
  P_SpeciesIris_virginica   : 0,
  _leaf_id_                 : 2 }

 

 


Score with CAS


Models can be published to different destinations. MAS is one of the destinations. The CAS server is also a valid destination.

The caslScore method is used to score models published to a CAS destination.

Run: caslScore

Syntax

    let r = await restaflib.caslScore(store, session, scenario);

The three arguments to this method are:

  • store - the restaf store

  • session - the CAS session object obtained via casSetup

  • payload - Information for scoring. See the description below.

The payload (see example below) has the following data:

  • the name of the model

  • the Caslib and name of where the model is stored (a sashdat)

  • the scenario for scoring

Example

 	async function example() {
        let {session} = await restaflib.casSetup(store);

        let scenario = {
            modelName: 'Gradient_Boosting_7adb0404_85e3_474d_9d03_1059ef7ae008',
            model    : { caslib: 'public', name: 'testpublish' },
            scenario : {
                sensor_ratio       : 4.3,
                days_out_of_service: 5
            }
        };
        let r = await restaflib.caslScore(store, session, scenario);
        console.log(JSON.stringify(r, null,4));
        }
The result is a Javascript object with the data returned from scoring. In the example above the result is:
{
    "days_out_of_service": 5,
    "sensor_ratio": 4.3,
    "_Index_": 1,
    "EM_EVENTPROBABILITY": 0.10521221221641,
    "I_FAILURE": "           0",
    "P_FAILURE0": 0.89478778778358,
    "P_FAILURE1": 0.10521221221641,
    "_WARN_": "",
    "EM_CLASSIFICATION": "           0",
    "EM_PROBABILITY": 0.89478778778358
}

 


Scoring with CAS Actions


In some scenarios, the models have been imported into a CAS server. The form of the models could be one of these:

  1. Astore objects

  2. Data step code for scoring

  3. DS2 code for scoring

 Use the caslScore method discussed earlier for scoring but with a different payload -- mainly not specifying the modelName

The method will decide the correct action based on the content of the model.

Run: caslScore

Syntax

    let r = await restaflib.caslScore(store, session, scenario);

The three arguments to this method are:

  • store - the restaf store

  • session - the CAS session object obtained via casSetup

  • payload - Information for scoring. See the description below.

The payload (see example below) has the following data:

  • The Caslib and name of where the model is stored (a sashdat)

  • The scenario for scoring

Example

async function example() {
	let scenario = {
		model   : { caslib: 'casuser', name: 'gradient_boosting___bad_2jest' },
		scenario: {
			LOAN   : 100000,
			VALUE  : 10000,
			JOB    : 'J1',
			CLAGE  : 100,
			CLNO   : 20,
			DEBTINC: 20,
			DELINQ : 2,
			DEROG  : 0,
			MORTDUE: 4000,
			NINQ   : 1,
			YOJ    : 10
		}
	};

	r = await caslScore(store, session, scenario);
	return r;
};

 


Embed VA reports


A very common scenario is embedding SAS Visual Analytics reports in web applications.

The HTML tag for embedding the report looks like this:

<sas-report url=your-viya-server reportUri=uri-to-the report
authenticationType="credentials"></sas-report>

To use this tag one needs the URI for the report. The following methods helps you get this information.

setup: addServices

Initialize access to the reports service. *Note, this is a method on the store.

Syntax

    let {reports} = await store.addServices('reports');

Run: getReportUri

This method returns the URI for the selected report.

Syntax

     let reportList = restaflib.getReportUri(store, name-of-your-report);

returns an array of the form [name-of-your-report, uri-for-that-report]

Example

Here is a sample code to set the properties for a sas-report tag with the uri of the report.

    objectElement =  document.getElementById('report0');

    objectElement.authenticationType ="credentials";
    objectElement.url                = host;/* the url for the Viya server*/
    objectElement.reportUri          = reportsList[0].uri;

 


Other common scenarios


Listing items

In many applications there is a need to list items(resources) like these: List of files, List of MAS models, List of VA reports etc... Below is an example for listing all the VA reports. Replace all references of "reports" to your favorite service(files, folders etc...)

async function listReports(store) {

  //connect to reports microservice and get root links

  let { reports } = await store.addServices('reports');

  //get the first list of reports

  let result= await store.apiCall(reports.links('reports'));

  console.log(JSON.stringify(result.itemsList(), null,4));

  // get the rest of the reports

  let next;

  // do this loop while the service returns the next link

  while ((next = result.scrollCmds('next')) !== null) {

    result = await store.apiCall(next);

  console.log(JSON.stringify(result.itemsList(), null,4));

  }

  return 'done';

};

 


Finally


SAS Viya REST APIs allow developers to do a lot more than what was discussed in this article. Users can use restaf and more wrapper libraries like restaflib to build full applications. These libraries are available as open source at https://github.com/sassoftware

Please feel free to clone these and modify them as you see fit.

 


Links to other examples


These are links to some  examples that might be useful for learning purposes:

  1. Web application demos https://github.com/sassoftware/restaf-uidemos/tree/master/public

  2. nodejs demos - [https://github.com/sassoftware/restaf/tree/2.0.0/packages/examples/testFunctions](https://github.com/sassoftware/restaf/tree/2.0.0/packages/ s/testFunctions)

  3. AWS serverless function - https://github.com/sassoftware/restaf/tree/2.0.0/packages/serverless-score/src

  4. Optimization application - https://github.com/sassoftware/restaf/tree/2.0.0/packages/serverless-score/src

Clone this wiki locally