Before can begin to write our job's source code we will need to complete a few setup steps. First we will create the job object and generate an HTML front end for it. To do this, start within the Job Execution WebApp and create a new job. In the screenshot below, I'm creating a job in the folder "/Public/JES_Example":
Give your job a name. In my example, the job is named "JES_APIAction".
All other options can remain as their default values. Press "OK":
With our job in place our next step will be to right click on your job and choose: "Edit" -> "New HTML form" and build the user interface that contains a drop-down list of Visual Analytics Reports from which a user can make a selection. This will be done by employing a script which dynamically creates the HTML drop down prompt by making a API request to the Viya Reports API and gets a collection of reports. Secondly, we will loop though each item in the API's response json file and iteratively convert it into the drop down list options. When the user selects a report from the drop down, the report's specific uri is sent to the JES job in the form of the SAS macro variable "&JESParameter". The code to do this is in this article's GitHub repository and is also covered in detail in my previous article Creating JES Prompts and Output Using the Viya Reports API.
If you like, you can take a look at what your HTML code looks like. To do this, right click on your job and choose: "Display form" -> "HTML form":
With our HTML code in place we can now begin to create the source code for our job to execute. You can add this by right-clicking the job and selecting: "Edit" -> "Source code":
From here you can add SAS Code to the job and save it. So now our JES application development can begin! The code examples and output in the next two sections are all the result of the user having selecting Visual Analytics report "Stock Market Analysis". After the user selects this report from the drop down the report's specific uri will be passed to the Job in the form of the SAS macro variable "&JESParameter".
* Base URI for the service call;
%let BASE_URI=%sysfunc(getoption(servicesbaseurl));
filename rep_ds temp;
proc http url="&BASE_URI/relationships/relationships/?resourceUri=/reports/reports/&JESParameter"
method='get'
oauth_bearer=sas_services
out=rep_ds;
run;
quit;
libname rep_ds json;
/* extract information about the data sources */
data getReportDataSources;
set rep_ds.items;
where index(relatedResourceUri,"/casManagement/servers")>0 and scan(relatedResourceUri,-2,'/')='tables';
reportCasLib = (scan(relatedResourceUri,-3,'/'));
reportCasTable = (scan(relatedResourceUri,-1,'/'));
caslibEndpoint = tranwrd(relatedResourceUri,trim('/tables/' || reportCasTable),'');
keep reportCasLib reportCasTable caslibEndpoint;
run;
%let BASE_URI=%sysfunc(getoption(servicesbaseurl));
filename clibinfo temp;
proc http url="&BASE_URI/casManagement/servers/cas-shared-default/caslibs/casuser/tables?limit=10000"
method='get'
oauth_bearer=sas_services
out=clibinfo;
run;
libname clibinfo json;
proc print data=clibinfo.items noobs;
var caslibname name state;
run;
proc sort data=getReportDataSources;
by reportCasLib reportCasTable;
run;
%macro getLoadedTables(caslibURI);
filename clibinfo temp;
proc http url="&BASE_URI/&caslibURI/tables?limit=10000"
method='get'
oauth_bearer=sas_services
out=clibinfo;
run;
libname clibinfo json;
data formatTable;
set clibinfo.items;
rename name = reportCasTable
caslibName = reportCasLib;
run;
proc sort data=formatTable;
by reportCasLib reportCasTable;
run;
Data getReportDataSources;
Merge getReportDataSources(in=T1) formatTable(in=T2);
If T1;
by reportCasLib reportCasTable;
keep reportCasLib reportCasTable state;
if state='' then state='unloaded';
else state=state;
run;
%mend getLoadedTables;
proc sql;
create table reportCaslibs as select distinct caslibEndpoint from getReportDataSources;
quit;
data _NULL_;
set reportCaslibs;
call execute('%getLoadedTables(' || trim(caslibEndpoint) || ')');
run;
title "The Report(s) Data Sources are Listed Below /reports/reports/&JESParameter";
proc report data=getReportDataSources nowd missing;
columns reportCasLib reportCasTable state flag;
define flag/computed noprint;
define ID / group 'Report ID';
define reportCasLib / group 'CAS Library';
define reportCasTable /Group 'CAS Table';
define state /Group 'CAS Table State';
compute flag;
if state='loaded' then do;
call define('_c3_', 'style','style={background=green foreground=white}');
end;
if state='unloaded' then do;
call define('_c3_', 'style','style={background=red}');
end;
endcomp;
run;
And here is our color coded output!The next step of our JES job will leverage a CAS Action to get more information about the report's data sources!
CAS actions are one of my favorite parts of the Viya ecosystem. If you are looking to preform data operations on data tables in Viya, you can use a CAS action to get the results you need very quickly. Since we are creating a job within the JES framework, you can easily access and run one or more of the CAS Action Sets that are loaded to your environment. For our current example we will be looking at the tableinfo action which provides a list of tables that are in memory on the server.
Since this action provides information on tables that are currently in memory, we must first ensure that at least one of the report's data sources is currently loaded. Since we were able to determine this for each data source in Step 1, we can use that information to our advantage. A simple data step with a where clause will return only observations with state of loaded from the getReportDataSources data set. Then we can use conditional logic to conditionally execute different sets of code. If there are no records in the getReportDataSources with a state of 'loaded' we will simply execute our proc report code from above. Otherwise, we will run the tableinfo CAS action. The code to do this is below:
data getReportDataSourcesCheck;
set getReportDataSources;
where state="loaded";
run;
data _NULL_;
if 0 then set getReportDataSourcesCheck nobs=n;
call symputx('nrows',n);
stop;
run;
%if &nrows=0 %then %do;
title "The Report(s) Data Sources are Listed Below";
proc report data=getReportDataSources nowd missing;
columns reportCasLib reportCasTable state flag;
define flag/computed noprint;
define reportCasLib / group 'CAS Library';
define reportCasTable /Group 'CAS Table';
define state /Group 'CAS Table State';
compute flag;
if state='loaded' then do;
call define('_c3_', 'style','style={background=green foreground=white}');
end;
if state='unloaded' then do;
call define('_c3_', 'style','style={background=red}');
end;
endcomp;
run;
%end;
%else %do;
/* run some cas actions! */
%end;
Nice! Now that we have determined at least one of the reports data sources is loaded, we can run the tableinfo actions against it. The table info action needs to be fed a caslib and table as part of its input parameters. One of the most useful features of CAS actions is the ability to use the saveresult statement to capture the output from the action to a SAS dataset. We will make use of this so we can apply the information we get from the tableinfo action to the main reporting data set our JES job is creating.
For a simple example, we can run the tableinfo action on the data set PUBLIC.STOCKS. While the tableinfo action provides a lot of details about the table, we will only print out the table's name, row/column count and its compressed flag (whether the table was loaded using the compress option)
proc cas;
table.tableInfo result=S
caslib="Public"
table="Stocks";
val = findtable(s);
saveresult val dataout=work.casTableInfo;
run;
proc print data=casTableInfo noobs;
var Name Rows Columns Compressed;
run;
The output is as follows:
Very interesting! The CAS action has told us both the table's size (row and columns) and also if it is compressed in memory. This could be very useful information when looking at a Visual Analytics Report's performance. We can translate our simple example into a macro and add it to the JES job we are working on. As before, we will use the getReportDataSources data set to dynamically call the macro by using the CALL EXECUTE routine.
The code to do this is below:
%macro getTableInfo(reportCasLib,reportCasTable);
ods exclude all;
proc cas;
table.tableInfo result=S
caslib="&reportCasLib"
name="&reportCasTable";
val = findtable(s);
saveresult val dataout=work.casTableInfo;
run;
ods exclude none;
data casTableInfo;
set casTableInfo;
reportCasLib = "&reportCasLib";
rename name = reportCasTable;
run;
proc sort data=casTableInfo;
by reportCasLib reportCasTable;
run;
Data getReportDataSources;
Merge getReportDataSources(in=T1) casTableInfo(in=T2);
If T1;
by reportCasLib reportCasTable;
keep reportCasLib reportCasTable state Rows Columns SourceCaslib SourceName ;
run;
%mend getTableInfo;
data getReportDataSourcesExe;
set getReportDataSources;
length code $ 1000;
code = '%getTableInfo(' || trim(reportCasLib) || ',' || trim(reportCasTable) ||')';
where state="loaded";
run;
data _NULL_;
set getReportDataSourcesExe;
call execute(code);
run;
With the above macro complete we are very close to being ready to print our getReportDataSources data set. However we will need to make one final upgrade to the data set by converting the "compressed" variable from numeric (0 or 1) to character (No or Yes). Now we are ready to use PROC REPORT to print the results with both color coding and proper numeric formatting. The code to do this is below:
data getReportDataSources;
set getReportDataSources;
if compressed=1 then compressDesc = "Yes";
else if compressed=0 then compressDesc = "No";
run;
title "The Report's Data Source Details are Listed Below";
proc report data=getReportDataSources nowd missing;
columns reportCasLib reportCasTable state Columns Rows compressDesc flag;
define flag/computed noprint;
define reportCasLib / group 'CAS Library';
define reportCasTable /Group 'CAS Table';
define state /Group 'CAS Table State';
define Columns/Group 'Columns' format=comma8.;
define Rows /Group 'Rows' format=comma8.;
define compressDesc / Display 'Data Compressed?';
compute flag;
if state='loaded' then do;
call define('_c3_', 'style','style={background=green foreground=white}');
end;
if state='unloaded' then do;
call define('_c3_', 'style','style={background=red}');
end;
if state='unloaded' then do;
call define('_c3_', 'style','style={background=red}');
call define('_c4_', 'style','style={ foreground=white}');
call define('_c5_', 'style','style={ foreground=white}');
call define('_c6_', 'style','style={ foreground=white}');
end;
endcomp;
run;
And that's it! We have successfully written all JES code to replicate the example! In my next article I will be building on today's example by employing the powerful Viya Statistical Procedures to gain even more insight into the Visual Analytics report's data sources!
On GitHub, you will find all the code discussed in this article. All code intended to be saved in a SAS® Job Execution Web Application 2.2 job definition within a Viya 3.5 environment.
On Github, you will find the following support files for this article:
Hi Mike,
I am a new SAS user trying to implement what you have done here. When I run the program and submit a report from the dropdown my program gets stuck loading with the blue ellipsis showing. The results of the program (the report's data sources with some details) even appear after a couple seconds, but I am unable to scroll through them or click anywhere because the program is stuck loading. Do you have any idea why this might be happening and how I can fix it?
Thanks so much,
Clay
Hi Clay!
Glad you found the article useful!
Since the report eventually renders the response HTML, it sounds like the job itself is completing…
So, I’m wondering where your issue might lie. Perhaps you could run a test with another JES Web Application Job to see if the behavior is the same?
Could you try running the sample “ODS Output with Embedded Graphics” that is located in the samples that are shipped with The Job Execution Web Application?
More details on the samples can be found here:
SAS Help Center: Samples
Note: To run the sample job you will need to make a copy of it. When you access the samples area, select the “ODS Output with Embedded Graphics” and press the “Copy to” button to copy it to a location (such as my folder). From there you should be able to access your “My Folder” location and run the sample job.
Thanks!
Hey Mike,
Thanks for the quick response! Yes I have found this and some of your other articles very useful in helping me start writing my own SAS programs. The job itself I do believe is completing because I am able to select retrieve last job output in the Job Execution Web App and it is accurate.
I should also mention that the behavior I described is happening when I run the job from SASStudio. When I try to run the job from the Job Execution Web App, the dropdown never populates and clicking the submit button does nothing.
I was able to successfully run the "ODS Output with Embedded Graphics" from both SASStudio and the Job Execution Web App and it behaved correctly. Still not sure what the issue could be.
Thank you,
Clay
I'm following up because I have some more information. All of the sample jobs that are shipped with the Job Execution Web App are working for me. I noticed they don't use Javascript so I wanted to see if that's where the issue lied, so I tried implementing the code from your article "Executing a JES Job Using Javascript". When I try to run it from the Job Execution webpage the submit button does not work. If I run it from SASStudio everything works properly! However, for some reason, when I click to edit the job and then try to run it from there, I get the same issue where my results are displayed but it does not finish loading in SAS and I am left with the blue ellipsis on my screen. To make things weirder, when I select to run the job in SASStudio and it works properly, then I select to edit the job and a new tab pops up, even if I don't interact with the new tab and go back to submit the form in the tab where the job was working properly before, it now experiences the issue where it does not stop loading in SAS. I'm pretty lost with how to approach this at this point, I have tried googling but have not found anything useful. I'm hoping you will have some advice! Thanks so much for your time!
Hi Clay,
That does sound interesting. I have been trying to replicate your issue within the JES Web Application of my own Viya install without success…
I think you’re right that the browser might be having difficulty with the Javascript execution. Could you try another experiment to see if this is the case?
Can you re-create this example from the SAS Documentation?
Keeping in mind that you will need to update the “/Folder/json1” value from the html’s following line to match the value of where you recreated the example:
formData.append("_program", "/Folder/json1");
Seeing how your JES version of this example reacts when it’s executed might be helpful in determining the source of your issue.
Thanks!
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!
Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning and boost your career prospects.