11-13-2024
Renato_sas
SAS Employee
Member since
10-31-2013
- 195 Posts
- 5 Likes Given
- 18 Solutions
- 110 Likes Received
-
Latest posts by Renato_sas
Subject Views Posted 624 11-13-2024 01:23 PM 1244 09-13-2024 09:38 AM 506 06-03-2024 06:10 PM 1246 05-30-2024 01:03 PM 2468 03-26-2024 09:36 AM 1002 02-14-2024 06:36 PM 2803 01-05-2024 08:45 AM 3194 01-04-2024 09:40 AM 3249 01-03-2024 10:03 AM 3287 01-02-2024 11:03 AM -
Activity Feed for Renato_sas
- Got a Like for Re: How to count Unique/Distinct values in VA? From parts of a spreadsheet. 11-22-2024 09:26 AM
- Liked POI- Optimizing Retail with Product Demand Forecasting for arunsenthil. 11-13-2024 03:18 PM
- Posted Re: Deploy DDC Implementation Files in SAS Content Server via SAS Viya GUIs on SAS Communities Library. 11-13-2024 01:23 PM
- Posted Re: SAS Visual Analytics Advanced Calculations (part 2 of 4): AggregateTable on SAS Communities Library. 09-13-2024 09:38 AM
- Tagged Using Text Input object to enter list of values for filtering on SAS Communities Library. 06-03-2024 06:19 PM
- Tagged Using Text Input object to enter list of values for filtering on SAS Communities Library. 06-03-2024 06:19 PM
- Posted Using Text Input object to enter list of values for filtering on SAS Communities Library. 06-03-2024 06:10 PM
- Posted Re: How to count Unique/Distinct values in VA? From parts of a spreadsheet on SAS Visual Analytics. 05-30-2024 01:03 PM
- Posted Re: Deploy DDC Implementation Files in SAS Content Server via SAS Viya GUIs on SAS Communities Library. 03-26-2024 09:36 AM
- Posted Viya Jobs that Create Multiple Output Files on SAS Communities Library. 02-14-2024 06:36 PM
- Got a Like for Re: Using list of values from Excel file to filtering VA report. 01-09-2024 08:05 AM
- Posted Re: Using list of values from Excel file to filtering VA report on SAS Visual Analytics. 01-05-2024 08:45 AM
- Posted Re: Deploy DDC Implementation Files in SAS Content Server via SAS Viya GUIs on SAS Communities Library. 01-04-2024 09:40 AM
- Posted Re: Deploy DDC Implementation Files in SAS Content Server via SAS Viya GUIs on SAS Communities Library. 01-03-2024 10:03 AM
- Posted Re: Deploy DDC Implementation Files in SAS Content Server via SAS Viya GUIs on SAS Communities Library. 01-02-2024 11:03 AM
- Posted Re: SAS VA - load List Control with a pre-selected value on SAS Visual Analytics. 12-08-2023 04:16 PM
- Got a Like for Re: Passing paramters to job execution. 11-23-2023 09:22 AM
- Posted Re: Passing paramters to job execution on SAS Visual Analytics. 11-22-2023 11:43 AM
- Got a Like for Re: Passing paramters to job execution. 11-21-2023 07:02 PM
- Posted Re: Passing paramters to job execution on SAS Visual Analytics. 11-21-2023 07:00 PM
-
Posts I Liked
Subject Likes Author Latest Post 83 34 346 56 13 -
My Liked Posts
Subject Likes Posted 1 05-30-2024 01:03 PM 2 01-05-2024 08:45 AM 1 11-22-2023 11:43 AM 1 11-21-2023 07:00 PM 1 04-29-2022 05:14 PM -
My Library Contributions
Subject Likes Author Latest Post 0 5 2 1 2
11-13-2024
01:23 PM
Hi @edwinvbe ,
I agree with you that being able to reference a more user-friendly URL would be nice. The information shared in this blog is just a "hack" of the Content Server to work as a Web Server. Having said that, it's more of a workaround to allow for quick prototypes utilizing interfaces that you might already have access to than an ideal solution that would depend on IT to help you deploy the DDC and its files in a Web Server.
If you look closer, you will notice that the image is the only file that is not deployed using the SAS Job Execution Web Application. This interface allows for some mime types that we leverage for html, css, and javascript files, but there is nothing for images. For images we've utilized the SAS Drive interface, which doesn't provide a custom URL to access content loaded through it, so we've used the image's URI to construct the URL, which leverages a different service called File Service.
Again, the Content Server was never meant to be used to service files like we are doing here, but it works, although with certain limitations as you've observed.
... View more
09-13-2024
09:38 AM
Hi @sas22337,
In Viya 3.5 and 4 you can do that by defining an Aggregated Data (not covered in the blog - the blog talks about AggregateTable function). Once you add German Sales calculated item in the aggregated data table, that value is no longer treated as a calculation. It becomes a fixed number and therefore if you filter Germany out, the values are preserved for the other rows in the table. The Aggregated Data can be created by clicking on the "Data source menu" icon on the side of the top left dropdown containing the list of tables used in the report, and selecting "New data from aggregation of <your source table>".
In the more recent Viya 4 releases, there is something new called dynamic parameters that can also be used to solve this challenge. This is my preferred way. It uses less memory and performs better than the proposed solution above. You need to create a parameter that takes an expression that results in the German sales value (same expression we used in the blog) as its source, then create a calculated item based on that parameter. This trick works because the parameter is not affected by filters, so the calculated item is isolated from filtering Germany out.
... View more
06-03-2024
06:10 PM
A customer once asked me how to use a text box to filter multiple values. He wanted to type a list of zip codes and use that list to filter other objects in the report. If he had Viya 2020.1.1 (December 2020) or later, I could have referred him to the search feature for List control objects, but he was running Viya 3.5, so we had to be creative and develop our own solution.
The solution was obviously an advanced filter applied to the object to be filtered, and the Contains operator was the fist thing that came up to my mind. In Viya 3.5, the syntax for Contains is the following:
'Parameter'p Contains 'ZipCode'n
This of course was the solution at a high level, but it didn’t take too long to realize we would need to:
Define a separator for the values in the list.
Take care of certain exceptions, such as when the Text Input control is empty.
Deal with blank spaces that could appear at the beginning and/or at the end of typed zip code values.
Deal with mixed upper and lower case characters to support all types of zip codes around the world that could contain letters.
Refine our requirements – for example: what happens if an incomplete zip code is typed, such as 234? Should we select all zip codes that contain 234 or should it be a full match? What about the opposite – user types 12345-678 and the table contains 12345 – should they match?
Here is what we have decided:
Using comma as the separator would work just fine, as comma is not a character used in zip codes.
If the Text Input control is left empty, then it should have the same behavior as Visual Analytics: no filter is applied.
Leading and trailing blanks for each of the values should be ignored, as they are not part of the zip code values being searched.
We would standardize in upper case characters only.
Typed values and values stored in the table should match entirely, so 12345 would not match 12345-678 and vice-versa.
To test if the input list of zip codes (a character parameter) is empty, we used the IsSet operator, and to ignore the filter if it’s empty, we used the expression 1 = 1 (which returns TRUE, as Visual Analytics doesn’t have a Boolean type):
IF ( 'Parameter'p IsSet )
RETURN (<expression>)
ELSE ( 1 = 1 )
Now we needed to work on the <expression>. To remove blanks, we used the RemoveBlanks function. We could have used the RemoveChars function instead, which is more generic and would work for other types of characters. To standardize the input, we applied the UpCase function:
UpCase(RemoveBlanks('Parameter'p, _All_))
Our <expression> then became:
UpCase(RemoveBlanks('Parameter'p, _All_)) Contains UpCase(RemoveBlanks('ZipCode'n, _All_))
Observe that the same transformations were applied to both sides, just to make sure we are comparing apples to apples.
For the full match of individual values, the operator Contains naturally takes care of cases where the user types 12345 on the list of values and the table contains a zip code like 12345-678 (it’s a no match as defined), but the opposite is not true:
“zipcodeA,12345,zipcodeB” Contains “12345-678” --> returns FALSE (not a match): correct
“zipcodeA,12345-678,zipcodeB” Contains “12345” --> returns TRUE (a match): incorrect
If the user types 12345-678 in the Text Input control, it would match zip code 12345, and potentially any other subset of the input value, such as those bad zip codes:
12345-
123
45-6
-678
-
7
etc…
To guarantee the full match of what was being typed, we had to search for the entire value, and an easy way to do that was to include characters to mark the beginning and the end of the values in the search string. Well, it turned out that by definition, all values were already separated by comma in the input text, so it naturally became the character to indicate start and end of values. All we had to do was to include the separator in the expression, by concatenating a comma before and after the zip codes stored in the table, using the operator Concatenate twice:
Concatenate(
',',
Concatenate(
UpCase(RemoveBlanks('ZipCode'n, _All_)),
','
)
)
After concatenating comma before and after:
“zipcodeA,12345, zipcodeB” Contains “,12345-678,” --> returns FALSE (not a match): correct
“zipcodeA,12345-678, zipcodeB” Contains “,12345,” --> returns FALSE (not a match): correct
This seemed all we had to do, except for one thing: if user types 12345-678 as the first or the last element on the search list, or if it’s the only element in the list, it will not contain the separator character, and valid zip code values will not be found:
“12345-678,zipcodeB” Contains “,12345-678,” --> returns FALSE (not a match): incorrect
“zipcodeA,12345-678” Contains “,12345-678,” --> returns FALSE (not a match): incorrect
“12345-678” Contains “,12345-678,” --> returns FALSE (not a match): incorrect
To fix that we needed to append a comma at the beginning and at the end of the entire typed input parameter string, exactly as we did before with the zip codes stored in the table:
Concatenate(
',',
Concatenate(
UpCase(RemoveBlanks('Parameter'p, _All_)),
','
)
)
After we did that:
“,12345-678,zipcodeB,” Contains “,12345-678,” --> returns TRUE (a match): correct
“,zipcodeA,12345-678,” Contains “,12345-678,” --> returns TRUE (a match): correct
“,12345-678,” Contains “,12345-678,” --> returns TRUE (a match): correct
Putting everything together, this is how the filter expression looked like (before Viya 2023.06 syntax):
IF ( 'Parameter'p IsSet )
RETURN ( Concatenate(',', Concatenate(UpCase(RemoveBlanks('Parameter'p, _All_)), ','))
Contains
Concatenate(',', Concatenate(UpCase(RemoveBlanks('ZipCode'n, _All_)), ',')) )
ELSE ( 1 = 1 )
Same expression starting with Viya 2023.06 syntax (basically IsSet and Contains are functions instead of operators and the key work _All_ no longer has underscores):
IF IsSet('Parameter'p)
RETURN Contains(
Concatenate(',', Concatenate(UpCase(RemoveBlanks('Parameter'p, All)), ',')),
Concatenate(',', Concatenate(UpCase(RemoveBlanks('ZipCode'n, All)), ','))
)
ELSE ( 1 = 1 )
In our case, zip codes were already stored as characters in the source table, but if that was not the case, we could simply apply the Format function to transform them into characters, and the rest of the expression would be the same.
Obviously, as long as the requirements remain the same, this expression can be leveraged for other use cases, such as searching for car makes.
Suppose that you have a bar chart, and you want to be able to type one or more vehicle Makes separated by comma in the Text Input control object at the top to filter the chart:
Figure 01-Text input and bar chart objects
Let’s take a look at how you would do that in three easy steps.
FIRST, you create a parameter with the following attributes:
Name: list of Makes separated by comma
Type: Character
Multiple values: unchecked
Current value: empty
Figure 02-Create character parameter
Knowing that this parameter will carry multiple values, you may be attempted to check the box for multiple values, but a text input control object, where this parameter is going to be assigned to receive its value, does not accept multiple values – the object is a text box, and therefore it accepts only a single value, which is a string.
SECOND, you assign the parameter to the Text Input control object. Note that before Viya 2024.05, the parameter is assigned in the object’s Roles pane:
Figure 03-Assign parameter to text input object prior to Viya 2024.05
Starting in Viya 2024.05, this assignment is done in the object’s Actions pane: Figure 04-Assign parameter to text input object in Viya 2024.05 and beyond
THIRD, you add the advanced filter discussed previously into the bar chart’s Filters pane – remember that the name of the parameter and the data item being searched are different in this example.
Now let’s suppose you type “Toyota,, , bmw, hoNdA ,Mercedes,Volks Wagen , xyz, Nissan SUV, Land rover”, without the quotation marks, in the Text Input object and hit Enter – you must hit Enter for the value to be assigned to the parameter. Starting from the inner most operators/functions and moving outwards, this is what happens:
Remove all blanks (space characters) from the input parameter. You should get this: Toyota,,,bmw,hoNdA,Mercedes,VolksWagen,xyz,NissanSUV,Landrover
Make everything upper case as a best practice for string comparison: TOYOTA,,,BMW,HONDA,MERCEDES,VOLKSWAGEN,XYZ,NISSANSUV,LANDROVER
Concatenate a comma at the end. Note that comma is used as the separator. It must be a character or combination of characters that do not appear in the data being filtered - all possible values that Make can assume in this example. This is what you have so far: TOYOTA,,,BMW,HONDA,MERCEDES,VOLKSWAGEN,XYZ,NISSANSUV,LANDROVER,
Concatenate a comma (or the other elected separator) at the beginning. Let’s call this result the standardized parameter: ,TOYOTA,,,BMW,HONDA,MERCEDES,VOLKSWAGEN,XYZ,NISSANSUV,LANDROVER,
Repeat the same steps for the Make column to create the standardized Make, so you can compare apples to apples. For example, if the value of the Make being compared is Land Rover, it would become: ,LANDROVER,
Check if the standardized parameter contains the searched standardized Make.
Only do all of that if the parameter is set. If it’s not set, return true, meaning all Make values should be displayed, which is the default in SAS Visual Analytics.
Figure 05-Results
Note that Mercedes did not show up in the bar chart because it’s stored as Mercedes-Benz in the table. In another hand, Nissan is a valid car make but Nissan SUV is not, so it was not returned. Also, because the implemented filter is not case sensitive, Honda was displayed as expected, even if typed with mixed casing in a strange way. Because spaces are eliminated, mistakenly typed Volks Wagen (two words), matched the make Volkswagen. Non-existing makes such as xyz, empty values, and extra blank spaces didn’t affect the results.
Please, let me know in the comments what you think about this solution and how or for what purpose you have used it. If you have Viya release 2020.1.1 (December 2020) or above, I’m particularly interested in knowing why you had to adopt this solution instead the List control object with search capabilities available out of the box.
... View more
- Find more articles tagged with:
- calculations
- expressions
- SAS Viya
- Tips and Tricks
- visual analytics
05-30-2024
01:03 PM
1 Like
I just came across this old question that for some reason was left unanswered. The solution is similar to what @Stu_SAS had posted before:
Total Family Members = AggregateTable(_Sum_, Table(_Sum_, Fixed("Family id"n), 1))
... View more
03-26-2024
09:36 AM
Hi @hyunwoo_kim ,
Have you set the content to JavaScript, as shown in figure 12?
... View more
02-14-2024
06:36 PM
5 Likes
If you have ever implemented a DDC that executes Viya Jobs, you certainly had to inspect the SAS log for clues about errors that might have happened, especially during the development phase, and even though you can ask the Viya Job to send back the SAS log, this most likely meant you had to modify portion of the JavaScript code that calls the job to set the parameter _debug=log, and in some cases the portion of the code that interprets the results received from the job as well.
When I first published the series of articles about integration of VA (SAS Visual Analytics) and Viya Jobs using DDC (Data-Driven Content object in VA), the third publication and the two subsequent use case examples used Viya Jobs that didn’t produce any visible output, but instead, they created tables that were loaded in CAS (memory). Because there was no visible output, the job ended up returning a small JSON structure to the DDC to inform the status of the SAS code execution, so after every SAS statement that could generate an error, we would call the macro function %stopOnError. This macro function does exactly what is says: it checks for errors in a few macro variables and if the error is found, it sends back to the DDC a small JSON message and aborts the job execution:
Figure 01-Example of old error message
Even though it works, there are some limitations on this approach:
You have to modify the SAS code to call the macro function after every step that may throw an error.
The error message, without a broader context or the exact location where the error occurred, may not be sufficient, which would still require to modify the code to call the job with _debug=log.
It only worked for Viya Jobs that didn’t send any content back to the DDC, such as SAS ODS (Output Delivery System) output, files, etc. If any other content had to be returned from the job to the DDC, then we could not send the JSON message back.
Well, it turns out that a Viya Job can return a list of output files in a JSON structure, and by leveraging that capability, we can present a new template of DDC + Viya Job that is a lot more flexible to return any content that you need, including the complete SAS log, while keeping the SAS code simple.
The advantages are:
You don’t need to modify the SAS code by adding macro function calls in strategic locations to capture eventual errors.
Any error in the SAS code is readily available in the SAS log file that can be retrieved and presented.
It works with any kind of Viya Job, independently of the number and type of output it generates.
You ultimately decide what to do with the output files: display in the web client (such as VA), download to your local computer, display in the browser’s console, etc.
Some disadvantages are:
The output files generated with the SAS code must be stored in a location where it can be retrieved, and that normally includes content generated with ODS output. That means you will need extra ODS statements in the SAS code to store the ODS output in files.
You can no longer send content to the DDC, such as ODS output that the browser understands, and hope the browser will automatically display it. You now control what to do with the different output files, which could also be an advantage (see #4 above) – it depends on how you look at it. You need to use JavaScript to post-process the JSON data and appropriately handle the list of output files, to give them their final destination.
Because a DDC is implemented as an HTML file, and a Viya Job can have a form (HTML file) associated with it, whenever there is a DDC that calls a job, I like to deploy the DDC as the job form and keep them together. The key difference between a DDC that calls a job and a job form, in terms of functionality, is that the DDC communicates with VA by sending and receiving messages with data, parameters, selections, etc. Everything else is the same. A DDC could for example receive data from VA and upload that data as a table to be processed by the Viya Job, and the job could load an output table in CAS, so other VA objects could visualize its content, like seen in this series of articles. To keep the focus on the key points of this example, which is creating and handling multiple output files that the Viya Job creates, we will not be using a DDC. Instead, we will be using a simple job form with a text box to capture the name of the input table and a button to submit the job. Again, the job form could easily be transformed in a DDC and integrate with VA.
The example explored here produces HTML and PDF output. The HTML is to be displayed and the PDF is to be downloaded, just to demonstrate that you are now in control of what to do with what the job produces as output:
Viya Job
Checks for expected parameter (input table in the format library.table) and returns an application error if something is wrong.
Produces the HTML and PDF output files (proc print of 10 first observations).
Job form (remember that it could be a DDC)
Acquires the input parameter (table name as library.table) and calls the Viya Job.
Checks the job output for errors.
If error, displays log information in the browser’s developer console
If ok, processes the job output files (displays HTML and downloads PDF)
The Viya Job Code
All files generated by the job to be returned to the job form (DDC) must be saved in the Viya Content Server via a special filename statement:
Figure 02-Filename statement
The name is important because this is how the job form (DDC) identifies this file in the JSON structure that it receives from the job, as you will see later.
Because the job can send back multiple files, I have adapted the old macro function %stopOnError (it’s ok if this is the first time you are hearing about this macro function) to send messages at the application level in a file named “_appout.json”. The macro was renamed to %sendApplicationMsg and is now used to inform errors that the application can identify, and abort the job execution if something is wrong. For example, we can use this macro in case an expected parameter was not passed to the job, or if a table doesn’t exist, etc. At the end, if the job runs successfully, you can also use that same macro function to inform the job form (DDC) that the job has reached the end and finished with success. Differently from the macro %stopOnError that had to be called multiple times in the SAS code, %sendApplicationMsg is only called if needed, according to the application logic. More precisely, in our example this macro function is used whenever a job input parameter is missing or the input table doesn’t exist. If you are wondering, we have a better mechanism to get information about unforeseen errors that will be explained later. This is an example of application message written to “_appout.json”:
Figure 03-Example of new application error message
The other two files that the job creates are named “_webout.html” and “_webout.pdf”, containing respectively the HTML and PDF output content. Those output formats are obtained via ODS. If you inspect the code, you will see that all ODS outputs are closed at the beginning of the code, and just before the proc print that generates a table with the first 10 observations is executed, two ODS outputs are open, one for HTML5 and one for PDF.
How those files are sent back to the job form (DDC) in a JSON structure depends on how the job is called, and it will be discussed in the next two sections: Calling the Job and Processing the Job Output.
This is the entire SAS code for our example:
Figure 04-Viya Job code
The Job Form (DDC) Code
Calling the Job
The URL of the job form (DDC) is the URL of the Viya Job, with the parameter _action=form, which we normally add in the job properties, so we don’t have to pass that parameter all the time, keeping the job URL simpler. When the job form (DDC) calls the job, we would normally overwrite _action=form by explicitly passing _action=execute. But when we want the job to return a list of output files in JSON format, we must call the job with _action=json. Making a call to the job with _action set to json is the first key change.
Figure 05-Calling Viya Job with HTML form and _action=json
This is an example of JSON with output files that you get when the job is called with _action=json:
Figure 06-Example of JSON response with list of job output files
Except for the log file, which name is automatically created for you, the other names are defined by you, and they are set in the SAS code with the filename statement.
There are two other parameters passed to the job that we need to highlight:
_resultfile: By default, only file names matching _webout.* are included in the JSON output. If you want anything else to be included, you need to inform in this parameter. Wildcards “*” and “?” are accepted and multiple values can be separated by “,” (see documentation for more details). if _resultfile is such that it allows the SAS log to be included, a file named <SYS_COMPUTE_JOB_ID>.log is automatically added to the output, where <SYS_COMPUTE_JOB_ID> is the job ID and matches a macro variable with the same name in the job code. The *.log file is in JSON format and it contains more than just the SAS log lines, therefore a bit of post processing is needed. The extra information can be used to color code the log, in case you want to display the log in HTML format for example.
_omittextlog: The default value of _omittextlog is true, but if it’s set to false, an additional SAS log file named <SYS_COMPUTE_JOB_ID>.log.txt is also automatically included in the output, as long as _resultfile permits. The *.log.txt file only contains the SAS log lines in text format and it’s easier to consume. We are not generating this file in our example.
Lastly, the header of the HTTP request must have “Accept” set to “application/json”, because this is what the Viya Job returns with _action=json.
In our example, all those steps are performed in the callJob() function (highlighted in yellow).
Figure 07-JavaScript function callJob()
Processing the Job Output
Once the job finishes and returns execution to the job form (DDC), you need to inspect its response as a JSON. But what if there was an error when the job was executing that you were not able to anticipate and inform it through a call to the macro function %sendApplicationMsg? Well, calling the job with _action=json has another advantage: if there is an error in the SAS code execution, a special JSON output is automatically sent back, according to the documentation:
Figure 08-Example of JSON response when there is an error in the SAS code
As you can see, the attributes in this JSON can be used to not only detect there was an error, for example, by inspecting the “errorCode”, but also to have additional insights on the error, including the entire SAS log. So once the job returns execution to the job form (DDC) and an error is detected, you can dump the SAS log in the browser’s developer console for easy troubleshooting (please see code highlighted in purple above). Obviously, you can skip this step after development is concluded and the solution is deployed in production.
Note: in Viya3.5, the response received when there is an error executing the SAS code is slightly different and is documented here. It contains not only the error message, but also the JSON structure with the result files that were specified in the _resultfile parameter. This is important because instead of reading the response as a JSON, you need to read it as a text and then parse its components into their own JSON objects.
If there was no error, the JSON structure received is actually the job output, with URI to the output files in it, as specified in the _resultfile parameter, so the next step is to inspect the content of the file we called _appout.json to see if there was no error at the application level. Remember that application errors are those that you generated when validating input parameters and are different from unexpected execution errors. If there was no application error, then you should access the output files URI, extract their content, and give them the proper destination: display the HTML and download the PDF. In our example, these are all performed in the Submit() function:
Figure 09-JavaScript function Submit()
To download the PDF file, you call the function downloadFile(). This function uses JavaScript to create a <a> element on the fly and simulate a user clicking in its hyperlink, which points to the PDF file’s URI.
Figure 10-JavaScript function downloadFile()
Deploying and Running the Example
The complete SAS code and job form are attached in a zip file. Once you add them to a Viya Job, you can run it directly from SAS Studio or SAS Job Execution web applications. Remember to set the parameter _action=form in the Viya Job properties.
The following two videos show how to create and run the job with the provided files in SAS Job Execution web application, and how to easily check the SAS log in the browser’s developer console (Ctrl+Shift+i) when there is an error in the SAS code.
... View more
- Find more articles tagged with:
- DDC
- JES
- VA-DDC-Jobs Integration
01-05-2024
08:45 AM
2 Likes
For your reference, here are some links to additional information that might be helpful (each one has multiple parts):
https://communities.sas.com/t5/SAS-Communities-Library/Data-Driven-Content-leveraging-third-party-visualizations-in-SAS/ta-p/437303
https://communities.sas.com/t5/SAS-Communities-Library/Introduction-to-Integration-of-SAS-Visual-Analytics-with-SAS/ta-p/670823
... View more
01-04-2024
09:40 AM
@OnkarD11, I'm not sure I understand what the problem is now. In your last screenshot you have the expected HTML output with all three elements: CSS, JS, and image. Besides the fact that your image doesn't have a transparent background, it looks good to me.
... View more
01-03-2024
10:03 AM
Do you see any error in the browser console?
It seems that the job doesn't exist. Have you created a job and added the html form into it?
... View more
01-02-2024
11:03 AM
Hi @OnkarD11 ,
When you added the *.js file, have you set its Content to JavaScript, as shown in figure 12?
Note: this option is only available for Viya releases post 3.5.
Best,
Renato
... View more
12-08-2023
04:16 PM
This is not possible with default VA control objects, but a DDC that implements an HTML form could be used to do it.
The DDC HTML would need to leverage CAS REST API to query the CAS table containing the desired report ID and pull the parameter settings from that report, supposedly stored in that CAS table. Because the custom HTML provides the prompts, you have the ability to manipulate them via JavaScript to pre-set the prompt values according to what had been saved.
I'm not saying it's simple, but it's definitely doable.
... View more
11-22-2023
11:43 AM
1 Like
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.
... View more
11-21-2023
07:00 PM
1 Like
@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
... View more
08-08-2022
10:09 AM
It's a nice idea, indeed. Like @Sam_SAS, I also thought about the gallery of visualizations in the documentation when I read about the challenge, which could be used as a guideline or source of inspiration. For a second pass on this challenge, showing how Options can modify each standard visualization could be an interesting exercise as well.
... View more
08-02-2022
11:02 AM
I was talking about the browser's DevTools console log (Ctrl+Shift+i)
... View more