BookmarkSubscribeRSS Feed
acordes
Rhodochrosite | Level 12

Calling my experts @XavierBizoux @Renato_sas 

 

I'm using job execution quite often, now I´m struggling with the easiest task of all in regard of all the use cases I hhave implemented so far.

 

I call a proc report code from VA using job execution. I do not need to pass any data, only some parameter values. 

But I'm lost how to do so in a dynamic way. 

Let's suppose that the report user has the ID='X12345' and the brand is 'Audi'. 

 

I have created parameters in VA with the same name but prefixed by underscore.

 

I thought using a web content object would suffice. 

This does not work. Or better said, the global macro variable make which I define in the job 'param' shows the value 'brand'p and not resolves to its value 'Audi'.

 

http://bsXYUZA.fs99.vxx.vwfs-ad/SASJobExecution/?_program=/Public/renato/LABOR/param&make='_brand'p

Furthermore I do not know if I have to set the parameter in the job execution itself under the options menu. 

 

%global make;

%put &make.;

data _null_;
file _webout;
put '<html>';
put '<head><title>Hello World! </title></head>';
put '<body>';
put "<h1>Hello %sysfunc(htmlencode(&make))!</h1>";
put '</body>';
put '</html>';
run;

 

 

 

 

10 REPLIES 10
Quentin
Super User

Sounds like this is a Visual Analytics question?  You might have better luck if you, or one of the admins, move this question to the visual analytics community.

The Boston Area SAS Users Group is hosting free webinars!
Next webinar will be in January 2025. Until then, check out our archives: https://www.basug.org/videos. And be sure to subscribe to our our email list.
acordes
Rhodochrosite | Level 12

I'm browsing through all samples provided by sas for the job execution.

'display macro variables'

'Hello world'

'Return SGPLOT Image Output'

...

 

But I cannot find a way to send a parameter that comes form the VA report and NOT from the input form. 

 

 

Quentin
Super User

It seems like more of a VA question.  Am I understanding correctly that you have a VA prompt named _brand, and the user might select the value "Audi"?  

 

Then I think the question would be how can you access the value of that prompt in your code?  

 

You tried:

http://bsXYUZA.fs99.vxx.vwfs-ad/SASJobExecution/?_program=/Public/renato/LABOR/param&make='_brand'p

But clearly '_brand'p is just text.  So you would want some way to have your VA report generate:

http://bsXYUZA.fs99.vxx.vwfs-ad/SASJobExecution/?_program=/Public/renato/LABOR/param&make=Audi
The Boston Area SAS Users Group is hosting free webinars!
Next webinar will be in January 2025. Until then, check out our archives: https://www.basug.org/videos. And be sure to subscribe to our our email list.
SASKiwi
PROC Star

@acordes - I've moved your post to the VA Community.

acordes
Rhodochrosite | Level 12

@XavierBizoux   Meanwhile I've tried to adapt the SAS example 'Simple ODS Html' by adding to the html the script block that in other usages captures successfully the parameters that are attached to the data driven content in VA through fake filters. 

 

The modified html looks like this:

It does not give an error, but the parameter is still missing. 

 

<!DOCTYPE html>
<html lang="en">

<head>
<title>Simple ODS HTML</title>
<link rel="stylesheet" href="/SASJobExecution/theme">
	<script type="text/javascript" src="https://bsxxxxx.fsyy.vwf.vwfs-ad/htmlcommons/util/messagingUtil.js"></script>
	<script type="text/javascript" src="https://bsxxxx.fsyy.vwf.vwfs-ad/htmlcommons/util/contentUtil.js"></script>
	<script type="text/javascript" src="https://bsxxxx.fsyy.vwf.vwfs-ad/htmlcommons/util/jobUtil.js"></script>
	
	<script>
		"use strict";
    	window.$ = window.parent.$;
		
		var _job_output_cas_table_v2 = null; 	// example: "CARS_COPY";
		var _job_executing_message_v2 = null; 	// example: "Waiting for job to finish...";
		var _job_name_v2 = null; 				// example: "/Public/Jobs/SAS Communities/HelloCASWorld"
		
        function onDataReceived(resultData)
        {
            if (resultData) {
				var resultName = resultData.resultName;
				
				var selections = va.contentUtil.initializeSelections(resultData); // good practice to remove eventual brush columns

				if (resultData.columns.length == 0) {
					// it needs at least one column and one row
					document.getElementById("JobResults").innerHTML = "";
					va.messagingUtil.postInstructionalMessage(resultName, "Please, assign roles");
					return;
				}
				
				var vaParameters = va.contentUtil.getVAParameters(resultData);
				_job_output_cas_table_v2  = vaParameters._job_output_cas_table_v2;
				_job_executing_message_v2 = vaParameters._job_executing_message_v2;
				_job_name_v2              = vaParameters._job_name_v2;
				if (!_job_executing_message_v2) _job_executing_message_v2 = "Executing job...";
				if (!_job_output_cas_table_v2 || !_job_name_v2) {
					// missing one or more parameters
					document.getElementById("JobResults").innerHTML = "";
					va.messagingUtil.postInstructionalMessage(
						resultName, 
						"Please, make sure the following parameters are being passed to this object:\n"+
						"(1) _job_name_v2\n"+
						"(2) _job_output_cas_table_v2\n"+
						"(3) _job_executing_message_v2 (optional)"
					);
					return;
				}
				
				document.getElementById("JobResults").innerHTML = _job_executing_message_v2;
				callJob(resultData)
					.done(function(jobOutput){
						if (jobOutput.success) {
							document.getElementById("JobResults").innerHTML = "Done!";
							setTimeout(() => {document.getElementById("JobResults").innerHTML = "";}, 2000);
						}	
						else {
							 document.getElementById("JobResults").innerHTML = "Job finished with ERROR (see console for details)";
						}
					})
					.fail(function(jqXHR, textStatus, errorThrown){
						document.getElementById("JobResults").innerHTML = "Job failed to execute (see console for details)";
					});
				
            }
        }
		
	    function callJob(resultData) {
			va.jobUtil.PrepareVADataForSASJobs (resultData);
			var stringifiedJSON = JSON.stringify(resultData);
	
			// Your large JSON object as a Blob - Blob is like a file, and the POST method sends the file to the server
			var jsonBlob = new Blob([stringifiedJSON], {type: 'text/plain'});
			
			// Create form with input parameters
			var formData = new FormData();
			formData.append("_program", _job_name_v2);
			formData.append("_action", "execute");
			formData.append("_output_type", "json");
			formData.append("myjsonfile", jsonBlob);
			formData.append("castab", _job_output_cas_table_v2);
			//formData.append("_debug", "log"); formData.append("_output_type", "text");
	
			return $.ajax({
					method: "POST", 
					url: "/SASJobExecution/", 
					data: formData, 
					contentType: false, // do not send content-type
					processData: false, // do not transform data to fit to the default content-type application/x-www-form-urlencoded
					headers:{"X-CSRF-TOKEN": "$CSRF$", "Accept":"application/json"}
				})
				.done(function(jobOutput){
					if (jobOutput.success) {
						console.log("Job executed with success!");
					}		
					else {
						console.log("Job executed with ERROR");
						console.log("jobOutput=", jobOutput);
					}
				})
				.fail(function(jqXHR, textStatus, errorThrown){
					console.log("************** JOB FAILED ***************");
					console.log("jqXHR: ",jqXHR);
					console.log("textStatus: ",textStatus);
					console.log("errorThrown: ",errorThrown);
				});
	    }
	    
        va.messagingUtil.setOnDataReceivedCallback(onDataReceived);

    </script>
	
</head>

<body role="main" class="jobexec_body">

<form class="jobexec_form" action="/SASJobExecution/" target="_tab">
<input type="hidden" name="_program"     value="$PROGRAM$"/>
<input type="hidden" name="_action"      value="execute"/>
<input type="hidden" name="_output_type" value="ods_html5"/>
<div class="jobexec_sample_header">SAS<sup>&#174;</sup> Job Execution</div>

<h1 class="jobexec_sample_name">Simple ODS HTML</h1>

<p>
The PRINT procedure creates a simple HTML page that displays the data in the
SASHELP.CLASS table.
</p>

<hr/>
<input type="submit" value="Run code" class="jobexec_sample_input_submit"/>
<input type="checkbox" name="_debug" id="_debug" value="log" class="jobexec_sample_input_checkbox"/><label for="_debug">Show SAS Log</label>

</form>

</body>

</html>

In the source code I want to use the parameter castab (I haven't changed the names in a meaningful manner so far) which from VA is sent as numeric- _job_output_cas_table_v2  parameter  with the value of 8. 

 

options VALIDVARNAME=any;

* This allows for the stopOnError macro function to run the sas commands after an error occurs;
options NOSYNTAXCHECK;

%macro stopOnError(msg);
  %put &=SYSRC  &=SYSCC  &=SYSFILRC  &=SYSLIBRC  &=SYSERR  SYSERRORTEXT=%superq(syserrortext)  &=MSG;
  %if (&msg eq ) %then %let msg=%superq(syserrortext);
  %if (&syserr > 6 or &msg ne ) %then %do;
    proc json out=_webout nosastags nopretty nokeys;
	  write open object;
	  write values "success" false;
	  write values "retcode" &SYSERR;
	  write values "message" "&MSG";
	  write close;
	run;
    cas mySession terminate;
    %let SYSCC=0;
    %abort cancel;
  %end;
%mend stopOnError;

%macro checkParams;
	%if (not %symexist(castab)) %then %stopOnError(Missing parameter CASTAB);
%mend checkParams;
%checkParams;

title "&castab.";
proc print data=sashelp.class noobs;
/* 	where age > &castab.; */
  var name sex age height weight;
run; quit;

 

 

 

Renato_sas
SAS Employee

@acordes, the line below assigns the value in _job_output_cas_table_v2 to a parameter called castab and castab becomes a macro variable in the SAS job code:

formData.append("castab", _job_output_cas_table_v2);

I can see that you used the code below to obtain the value of _job_output_cas_table_v2 from a VA parameter:

_job_output_cas_table_v2  = vaParameters._job_output_cas_table_v2;

 Question for you:

Have you performed the steps in VA that guarantee that the VA parameter is passed to the HTML code?

Using parameters with Data-Driven Content in SAS Visual Analytics - SAS Support Communities

acordes
Rhodochrosite | Level 12

Hi @Renato_sas , 

that's exactly my problem.

I use your code quite often, the one that underpins your pareto example use case.

Now I want to send a parameter but additionally to a input form setting like the the Html ODS output from the sas viya job exec samples. 

i tried to combine the html form with the ProxyDDCForVAJobCASIntegration. 

 

And I wonder if can alternatively add the parameter value to the URL like &myVar='myParameter'p.

 

Renato_sas
SAS Employee

The ProxyDDCForVAJobCASIntegration does not have a <form> tag because all the information it needs is coming from VA via DDC message and the form is dynamically created via JavaScript.
If you want to have prompts defined directly in the HTML, in addition to those coming from VA, you can have them, but not in a form. The form would submit the parameters to a pre-defined URL, but you still need to add the parameters from VA, so you would need the help of JavaScript for that.

I've modified (but have not validated/tested) your previous example to have a text box, a check box and a button in the HTML, and I've also added some comments. You are already setting many parameters via JavaScript, so I'd only add the extra parameters you need that come from the HTML directly, which in this example is the value in the text box. Also, the job html output will overwrite the HTML content (button, text, and check box). To have the output added below the HTML content, you would need to also return the job output in a list of files (a json structure) and use JavaScript to retrieve the output and display it in the desired location in the HTML (SAS Help Center: Returning a List of Output Files in JSON Format)

 

<!DOCTYPE html>
<html lang="en">

<head>
<title>Simple ODS HTML</title>
<link rel="stylesheet" href="/SASJobExecution/theme">
	<script type="text/javascript" src="https://bsxxxxx.fsyy.vwf.vwfs-ad/htmlcommons/util/messagingUtil.js"></script>
	<script type="text/javascript" src="https://bsxxxx.fsyy.vwf.vwfs-ad/htmlcommons/util/contentUtil.js"></script>
	<script type="text/javascript" src="https://bsxxxx.fsyy.vwf.vwfs-ad/htmlcommons/util/jobUtil.js"></script>
	
	<script>
		"use strict";
    	window.$ = window.parent.$;

		vat _resultData = null;
		var _job_output_cas_table_v2 = null; 	// example: "CARS_COPY";
		var _job_executing_message_v2 = null; 	// example: "Waiting for job to finish...";
		var _job_name_v2 = null; 				// example: "/Public/Jobs/SAS Communities/HelloCASWorld"
		
        function onDataReceived(resultData)
        {
            if (resultData) {
			    _resultData = resultData; // added this line to save the message received from VA to be reused when the button is clicked
				var resultName = resultData.resultName;
				
				var selections = va.contentUtil.initializeSelections(resultData); // good practice to remove eventual brush columns

				if (resultData.columns.length == 0) {
					// it needs at least one column and one row
					document.getElementById("JobResults").innerHTML = "";
					va.messagingUtil.postInstructionalMessage(resultName, "Please, assign roles");
					return;
				}
				
				var vaParameters = va.contentUtil.getVAParameters(resultData);
				_job_output_cas_table_v2  = vaParameters._job_output_cas_table_v2;
				_job_executing_message_v2 = vaParameters._job_executing_message_v2;
				_job_name_v2              = vaParameters._job_name_v2;
				if (!_job_executing_message_v2) _job_executing_message_v2 = "Executing job...";
				if (!_job_output_cas_table_v2 || !_job_name_v2) {
					// missing one or more parameters
					document.getElementById("JobResults").innerHTML = "";
					va.messagingUtil.postInstructionalMessage(
						resultName, 
						"Please, make sure the following parameters are being passed to this object:\n"+
						"(1) _job_name_v2\n"+
						"(2) _job_output_cas_table_v2\n"+
						"(3) _job_executing_message_v2 (optional)"
					);
					return;
				}
				
				/* transfer this block to new function submit() defined below, but change it slightly  
				   to remove access to the json information that is no longer returned in this example */
				/*
				document.getElementById("JobResults").innerHTML = _job_executing_message_v2;
				callJob(resultData)
					.done(function(jobOutput){
						if (jobOutput.success) {
							document.getElementById("JobResults").innerHTML = "Done!";
							setTimeout(() => {document.getElementById("JobResults").innerHTML = "";}, 2000);
						}	
						else {
							 document.getElementById("JobResults").innerHTML = "Job finished with ERROR (see console for details)";
						}
					})
					.fail(function(jqXHR, textStatus, errorThrown){
						document.getElementById("JobResults").innerHTML = "Job failed to execute (see console for details)";
					});
				*/
            }
        }
		
		function submit() {
				document.getElementById("JobResults").innerHTML = _job_executing_message_v2;
				callJob(_resultData);
					/*
					.done(function(jobOutput){
						if (jobOutput.success) {
							document.getElementById("JobResults").innerHTML = "Done!";
							setTimeout(() => {document.getElementById("JobResults").innerHTML = "";}, 2000);
						}	
						else {
							 document.getElementById("JobResults").innerHTML = "Job finished with ERROR (see console for details)";
						}
					})
					.fail(function(jqXHR, textStatus, errorThrown){
						document.getElementById("JobResults").innerHTML = "Job failed to execute (see console for details)";
					});
					*/
		}
		
	    function callJob(resultData) {
			va.jobUtil.PrepareVADataForSASJobs (resultData);
			var stringifiedJSON = JSON.stringify(resultData);
	
			// Your large JSON object as a Blob - Blob is like a file, and the POST method sends the file to the server
			var jsonBlob = new Blob([stringifiedJSON], {type: 'text/plain'});
			
			// Create form with input parameters
			var formData = new FormData();
			formData.append("_program", _job_name_v2);
			formData.append("_action", "execute");
			formData.append("_output_type", "html"); // the SAS code no longer returns json - ti now produces am html output
			formData.append("myjsonfile", jsonBlob);
			formData.append("castab", _job_output_cas_table_v2);
			
			// add/process information from html prompts:
			formData.append("mytext", document.getElementById("mytext")); // mytext will become a macro variable in the SAS code
			var showLog = document.getElementById("_debug").checked;
			if (showLog) {
			  formData.append("_debug", "log"); 
			}
	
			return $.ajax({
					method: "POST", 
					url: "/SASJobExecution/", 
					data: formData, 
					contentType: false, // do not send content-type
					processData: false, // do not transform data to fit to the default content-type application/x-www-form-urlencoded
					headers:{"X-CSRF-TOKEN": "$CSRF$", "Accept":"application/json"}
				})
				.done(function(jobOutput){
					if (jobOutput.success) {
						console.log("Job executed with success!");
					}		
					else {
						console.log("Job executed with ERROR");
						console.log("jobOutput=", jobOutput);
					}
				})
				.fail(function(jqXHR, textStatus, errorThrown){
					console.log("************** JOB FAILED ***************");
					console.log("jqXHR: ",jqXHR);
					console.log("textStatus: ",textStatus);
					console.log("errorThrown: ",errorThrown);
				});
	    }
	    
        va.messagingUtil.setOnDataReceivedCallback(onDataReceived);

    </script>
	
</head>

<body role="main" class="jobexec_body">

<div class="jobexec_sample_header">SAS<sup>&#174;</sup> Job Execution</div>

<h1 class="jobexec_sample_name">Simple ODS HTML</h1>

<p>
The PRINT procedure creates a simple HTML page that displays the data in the
SASHELP.CLASS table.
</p>

<hr/>
<label for="mytext" id="mytext_label">Any text:</label><br>
<input type="text" id="mytext" name="mytext"><br><br>
<!-- this next line was replaced with the one below it -->
<!--input type="submit" value="Run code" class="jobexec_sample_input_submit"/-->
<button onclick="submit()">Run Code</button>
<input type="checkbox" name="_debug" id="_debug" value="log" class="jobexec_sample_input_checkbox"/><label for="_debug">Show SAS Log</label>
<!-- I've added this div tag to be able to dsiplay the execution messages and show the Job output without overwriting the button and the check box -->
<div id="JobResults"></div>
</body>

</html>

 

I've also removed the portion that returns json from the SAS code, as your job is now returning HTML output. You could return both, but this would make the solution quite different from what you are familiar with:

title "&castab. &mytext.";
proc print data=sashelp.class noobs;
/* 	where age > &castab.; */
  var name sex age height weight;
run; quit;

BTW, I intend to write a blog about jobs that return a list of files - for example, to handle json messages returned from the job to indicate the status of the execution and other job outputs - but this will most likely be in January 2024.

acordes
Rhodochrosite | Level 12

I cannot test it right now. 

I'll give you feedback once I try its implementation. 

 

Meanwhile I have resorted to a workaround that does what I want it to do.

 

I use the Data-Driven-Content in junction with the ProxyDDCForVAJobCASIntegration. 

What I initially wanted to send as parameters, like the user ID and the brand, now make their way as data items.

In the job's code I write them to a data set. 

Then I bring them back into being as parameters using call symputx.

these parameters finally filter my proc report code and I ODS output the report to the user's folder who played the request in Visual Analytics. 

 

I do not feel comfortable with the javascripts and html forms, but until now I have always managed to make it work. Without your valuable support and those of @XavierBizoux  that wouldn't have been possible.

 

May I propose you another direction for an additional blog post? It would be great if you can clarify the following. For me within the whole job execution subject it's not clear what solutions are best suited for which use case. There are pure ones and those that combine techniques like mine here which builds up confusion. 

 

  1. Data Driven Content (DDC) to send data from Visual Analytics to a file
  2. DDC that opens an input form and passes its parameters to the code for the purpose of running a sas programm like proc print or proc sgplot
  3. List table which calls via URL a webpage and sends parameters with the value-target option of the list table and the &CarModel=SelectedModel added at the end of the URL

What important use cases I'm leaving out?

 

 

 

 

 

 

XavierBizoux
SAS Super FREQ

Hi Arne, 

I think you may be interested by the following blogs: 

https://communities.sas.com/t5/SAS-Communities-Library/Add-rating-functionality-to-your-Visual-Analy...

https://communities.sas.com/t5/SAS-Communities-Library/How-to-customize-data-export-in-SAS-Visual-An... 

 

Regarding the URL creation, here are the steps to handle them in VA: 

  1. Create a list table.
  2. Add columns you need plus hyperlink to Columns or Hidden role.
  3. In the Actions pane, select the URL Links heading, and then click New URL Link. The Add URL Link Action window is displayed.
  4. Enter a name and leave the URL field empty.
  5. Add a parameter. Select the Source to be a column with the hyperlink.
  6. Click OK.

You may have to generate the hyperlink column using calculated item. 

 

Please let me know if you need clarification on these. 

Regards, 

Xavier

Xavier BIZOUX
Advisory Technical Architect ● Global Enablement and Learning
Technology Transfer and Governance, R&D

SAS Innovate 2025: Register Now

Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!

Tips for filtering data sources in SAS Visual Analytics

See how to use one filter for multiple data sources by mapping your data from SAS’ Alexandria McCall.

Find more tutorials on the SAS Users YouTube channel.

Discussion stats
  • 10 replies
  • 3965 views
  • 3 likes
  • 5 in conversation