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;
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.
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.
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
@acordes - I've moved your post to the VA Community.
@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>®</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;
@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
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.
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>®</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.
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.
What important use cases I'm leaving out?
Hi Arne,
I think you may be interested by the following blogs:
Regarding the URL creation, here are the steps to handle them in VA:
You may have to generate the hyperlink column using calculated item.
Please let me know if you need clarification on these.
Regards,
Xavier
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!
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.