How to build a Custom Elements service for SAP Web Intelligence 4.2
DISCLAIMER: The SCN, Content, and Services are being provided to You AS IS. To the fullest extent allowable by law, SAP does not guarantee or warrant any features or qualities of the SCN, Content, or Services or give any undertaking with regard to any other quality. Statements and explanations to SCN, Content or Services in promotional material or on SCN and in the documentation are made for explanatory purposes only; they are not meant to constitute any guarantee or warranty of certain features. No warranty or undertaking shall be implied by a User from any published SAP description of or advertisement except to the extent SAP has expressly confirmed such warranty or undertaking in writing. Warranties are validly given only with the express written confirmation of SAPs management.
One of the great features delivered in Web Intelligence 4.2 is the “Custom Elements” feature. Custom Elements are external visualizations defined by you or by third-parties and which can be used in your Web Intelligence reports, like any native chart.
A Custom Element can be anything you want: a new type of chart or table, or any other kind of visualization, as long as it complies with the following rules:
- Support for at least one media type output – text/HTML is preferred, bitmap format is also recommended to be able to print or publish the Custom Element to PDF or Excel
- Support for the Web Intelligence metadata and data feeding model
- (In a future release) Support for the Web Intelligence settings model
Multiple Custom Elements can be delivered by a same service. This service is accessed through a simple HTTP URL, managed from the BOE Central Management Console.
Public APIs have been defined to support the communication between the Web Intelligence clients and the Custom Elements service. See the documentation on SAP Help Portal: http://help.sap.com/businessobject/product_guides/sbo42/en/sbo42sp1_webisl_dev_guide_en.pdf
Pre-requisites for this sample
This sample is based on Google Charts (see https://developers.google.com/chart/) and uses some open source software as well as a proprietary JavaScript application to wrap the Google Charts API in a Custom Elements service.
Get the necessary open source software
In this sample, we chose to run the service on a very simple JavaScript server called NodeJS. It is available for free on NodeJS website: http://nodejs.org.
Download and execute the MSI file for Windows (in this sample we are assuming the installation is done on Microsoft Windows). Note that for the purpose of this sample, we have used NodeJS v5.1.0.
In order to create a bitmap output from your Custom Elements, you will also need to install PhantomJS, available on the PhantomJS website: http://phantomjs.org. It comes as a ZIP file:
- Extract the EXE file from the ZIP
- Paste it into the NodeJS folder (“C:\Program Files\nodejs”, by default)
Finally, you will also need a few NodeJS plugins to execute the sample application. To download and install these plugins:
- Open a command window in Administrator mode in the NodeJS folder
- Set the npm proxy: this is mandatory if you access the internet through a proxy server, since the following instructions will download additional packages:
npm config set proxy “http://your_proxy:port“
3. Type in the following instructions (do not copy and paste, to prevent special characters from being copied):
npm install phantom-proxy (necessary to use phantomJS from nodeJS)
npm install xmldoc (necessary to parse the XML code)
npm install body-parser (necessary to parse the commands from Web Intelligence)
npm install pm2 –g (PM2 is a NodeJS process manager and is mandatory to manage your Custom Elements service)
Note that there are a few errors and warnings when installing these plugins but they are not blocking:
If the instructions “npm install” run in a few seconds, then it is very likely that the proxy setting is incorrect. Typically, these instructions should take a few minutes to download and install each additional package. All plugins are installed in the “node_modules” sub-folder of NodeJS.
Build your Custom Elements service
Create a JavaScript program to use the Google Charts API
In the NodeJS folder, save the “CustomElementsGoogleCharts.txt” file attached to this article and change its file name extension into “CustomElementsGoogleCharts.js”. This application contains the necessary JavaScript code to use a limited set of the Google Charts API as Custom Elements in Web Intelligence documents.
You will need to modify this code to choose a port number. For instance:
// Set the port number
app.listen(8095);
Start the sample
Using PM2, you are now ready to start your Custom Elements service.
- Open a command window in the NodeJS folder.
- Type in the following command:
pm2 start “CustomElementsGoogleCharts.js”
If successful, you should now see in the command window a table showing CustomElementsGoogleCharts running as a service (pid value might differ from the example below):
Concerning the PM2 NodeJS process manager, the following commands can be useful:
pm2 list Shows all processes installed on your NodeJS server
pm2 stop <App name|id|all> Stops all or the specified JavaScript application
pm2 restart <App name|id|all> Restarts all or the specified JavaScript application
pm2 delete <App name|id|all> Removes all or the specified JavaScript application from the server
pm2 reload <App name|all> Reloads all or the specified JavaScript application – useful after you have modified the application
Test your Custom Elements service
Test the service in a browser: http://your_service:port (no backslash at the end of the URL!). The port number is the one set in your service.
If all goes well, you should see this message in your browser: “Server up and running!” If you don’t see that message, check the NodeJS error log file stored in C:\Users\your_user_account\.pm2\logs.
More information on how to debug NodeJS applications can be found on the GitHub Web site: https://github.com/joyent/node/wiki/Using-Eclipse-as-Node-Applications-Debugger
Plug your Custom Elements service into Web Intelligence
This last step is the easiest! In order to use your Custom Elements service in Web Intelligence, you need to declare that service in the BI Platform Central Management Console (CMC).
In the Custom Elements tab of the Web Intelligence application parameters:
- In the CMC, click on “Applications”, right-click on “Web Intelligence” and select “Properties”
- In the “Properties” dialog box, click on the “Custom Elements” tab and then click on “Add Service…”
- Give a name to your service
- Enter its URL. This should be “http://your_service:port” (no backslash at the end of the URL!)
- Click on “Test” to make sure the service is alive and correctly answers all tests. If it does, then the supported media should be displayed.
- Keep the default “Element Format” value and click OK.
- On the next screen, make sure you click in the checkbox to enable your service, then save and close the window.
- Next time you edit a Web Intelligence document on that BI Platform you should see the Custom Elements button in the Web Intelligence toolbar. If you click on that button, you should be able to insert one of the proposed Google Charts in the document you are editing.
That’s it!
Wonderful document, thank you so much Pascal
With this I was able to build this
Hello
thanks for the post
have you post the way to do this
have you try with d3js ?
thanks a lot
Hi Matthew,
do you have posted somewhere a How-to guide to perform this beautiful Sankey diagram ?
Thanks in advance.
Rgds
Pierrick
Hello Pierrick, Sorry I don't. I think if you follow this blog, you should be able to do it. I did. Regards, Matthew
Hi Matthew,
could you provide me the CustomElementsGoogleCharts.js file, because I am able to display a Google Pie chart but not a Sankey Diagram.
Thank you in advance.
Pierrick
Great. This looks very promising. I am sure that this will make WebI a solid candidate for advanced visualization pruposes with outstanding calculation capabilities!
Would be possible to include (in the future) another sample of custom element service based on the other well-known API visualization: D3.js
Thanks
It really would be great to see something based on D3, as quite a few Lumira extensions are based on D3 as well. It would let us see how easy/difficult it will be to leverage the work the Lumira dev community has put in.
Thank you Hayden and Alfons. Over the next week or so I'm going to try and take a Lumira Extension and bring it into Webi. Webi needs a 'wrapper' around it, but in theory its quite easy. I'll keep you posted! Regards Matthew
That would be fantastic, especially if you can walk us through the steps required/where the two interfaces differ.
Hi Matthew,
Any update about the D3 based sample? This google chart implementation is a good demo of charting capabilities but is restricted to charts delivered by Google.
Thx
The official documentation on how to make the corresponding web service is a bit vague, so this is an excellent and understandable example.
From what I have heard, this was also presented at the DSUG event last month with a more extended example. It would be nice if that presentation could be shared here on SCN as well 🙂
Hello Thomas,
At the DSAG, we have shown early previews of what some of SAP partners are currently building with this new feature... 🙂
Galigeo WebIntelligence Custom Elements - YouTube
Embedding CMaps Analytics in Webi 4.2 - YouTube
Pascal.
Hi Pascal,
From the user guide, I found that there is a query parameter 'locale'. So I want to know how to use this query parameter, do you have any ideas?
If you want a better integration with Web Intelligence, you should use this parameter to localize your response.
For example, when Web Intelligence retrieves the list of supported visualizations your service will be called using the following URL:
http://hostname:port/api/visualizations?locale=en_US
In this case, you should provide a JSON response with localized name and description.
Works great, thank you Pascal!
I'm trying to create a custom element using .NET. For the render, the documentation is contradictory. It says:
Request
URI: api/visualizations/render<vizID>/render
HTTP Method: POST
Then it gives the example:
Request Example:POST /api/visualizations/funnel/feeds/render?locale=en_US
I'm having trouble getting it to Render. Could you let me know what the correct path should be?
Hello Craig,
This is en error in the documentation. It has been corrected in the next version (not yet available).
The documentation should read:
URI: api/visualizations/<vizID>/render
And the example is:
POST /api/visualizations/funnel/render?locale=en_US
Regards,
Pascal.
I'm not able to create more than 2 dimensional feeds. When I do, there is an informational message at the top of the custom element saying that only 2 of type dimension are allowed. I'm trying to create a Google Gantt chart, which needs Task Name, Start Date and End Date at a bare minimum. Why is there a limit on dimensions? The Gantt chart could use up to 7 dimensions.
Hello Craig,
Although you can define and use as many feeds as you want, the number of dimensional axis is limited to 2. This is because the Custom Elements feeding model is based on the Web Intelligence chart engine feeding model. In all WebI charts, the feeding works like in a cross-table, i.e. with a maximum of 2 axis.
Regards,
Pascal.
Hello Craig,
You can have as much dimensional feed as you want, but each dimesional feeds must be bound to one of our 2 axis: the dataset model of CustomElement is the same as the one for CrossTable.
Regards,
Arnaud
Ah. Thank you for that clarification. I reviewed the CrossTable and saw that I can bind multiple dimensions to a single axis. That was what I needed to pass multiple dimensions to the Google Gantt chart, and I've now got it working! Thank you!
Hi Craig, where did you find the documentation on the CrossTable ? Thanks
I didn't actually look at documentation. I just remembered that for many charts in general, you can add more than 1 dimension to a single axis. The Web Intelligence user guide might document this, I'm not sure. The Webi charting engine tries to interpret all the data and pass what would be the final representation on a chart. For my Gantt chart, that would not work because I need more than 2 dimensions and I don't want Webi to combine/interpret the data. I just need the raw data for each dimension, and the order of the rows needs to be preserved across the data so that I can reconstruct a row with multiple columns in my code. Using multiple dimensions on a single axis accomplishes this. I'm posting a screenshot in response to Arnauds request below.
Thank you Craig.
Indeed, Custom Elements use the same feeding model than WebI traditional charts and this model is based on a cross-table, i.e.: two axis with as many dimensions as you wish on each axis.
You're welcome.
Can you share a screenshot of the result ?
Where did you get the gantt chart visualizations? Can that be exported to Excel and PDF?
Hello Craig,
do you mind to share how you did it?
Can you please share your js file?
Best regards
Perhaps a small, but important note! This solution will work in Web Intelligence, but not in Web Intelligence Rich Client. When using Web Intelligence Rich Client, the visualisation will appear blank.
Hi Matthew,
This is because the Custom Elements service goes through the BOE server where it has been configured. The Rich Client therefore needs to be connected to that BOE server in order to show the Custom Elements visualizations.
Best,
Pascal.
Thanks a lot, great guide.
I have not been able to get it to work with WebI Java Applet - it only worked with the HTML version (although I can view the custom elements via the applet interface - I cannot create a new custom element via Applet). Using 4.2 SP2.
com.businessobjects.rebean.wi.exception.CustomElementExceptions$CustomElementAvailableTypesException: An exception occured when retrieving the feeding metadata from the CustomElement third party service. (Error: RWI 00634)
I can't understand what is going on with the data that Webi is passing to the custom element. Here is a picture of the table, and below is the JSON from Fiddler of the render HTTP POST.
Oddities:
1. It is not combining the two "5/9/16" values. I think this may be a leading or trailing space in my data
2. How do I relate Task Name with Task Start Date?
3. The measures are even more crazy. I have no idea what to make of them or how to deal with them!
{
"width": 400,
"height": 300,
"dpi": 96,
"font": {
"name": "Arial",
"size": 9,
"color": "#333333",
"isBold": false,
"isItalic": false
},
"feeding": [
{
"id": "task-name",
"expressions": [
{
"dataId": 1
}
]
},
{
"id": "start-date",
"expressions": [
{
"dataId": 3
}
]
},
{
"id": "duration",
"expressions": [
{
"dataId": 5
}
]
},
{
"id": "percent-complete",
"expressions": [
{
"dataId": 7
}
]
}
],
"data": [
{
"values": {
"dataType": "string",
"dataStructure": "simple",
"rawvalues": [
"DMR,
"DMR Ops",
"IMR",
"PjMR",
"PMR",
"PMR Ops",
"PPR",
"SMR for Partners",
"SMR for Services"
],
"cardinality": 1
},
"id": "1",
"title": "Task Name",
"type": "dimension"
},
{
"values": {
"dataType": "string",
"dataStructure": "simple",
"rawvalues": [
"2/1/16",
"4/11/16",
"5/9/16",
"5/9/16",
"5/18/16",
"6/8/16"
],
"cardinality": 1
},
"id": "3",
"title": "Task Start Date",
"type": "dimension"
},
{
"values": {
"dataType": "double",
"dataStructure": "simple",
"rawvalues": [
[
128,
1.7e+308,
1.7e+308,
123,
65,
1.7e+308,
53,
1.7e+308,
1.7e+308
],
[
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
0,
1.7e+308,
1.7e+308,
1.7e+308
],
[
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
4
],
[
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
0,
1.7e+308
],
[
1.7e+308,
1.7e+308,
0,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308
],
[
1.7e+308,
6,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308
]
],
"cardinality": 2
},
"id": "5",
"title": "Task Duration (days)",
"type": "measure"
},
{
"values": {
"dataType": "double",
"dataStructure": "simple",
"rawvalues": [
[
8,
1.7e+308,
1.7e+308,
3,
6,
1.7e+308,
25,
1.7e+308,
1.7e+308
],
[
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
11,
1.7e+308,
1.7e+308,
1.7e+308
],
[
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
13
],
[
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
9,
1.7e+308
],
[
1.7e+308,
1.7e+308,
18,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308
],
[
1.7e+308,
14,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308,
1.7e+308
]
],
"cardinality": 2
},
"id": "7",
"title": "Task Percent Complete (fake data)",
"type": "measure"
}
]
}
Hello Craig,
There are a lot of NaN values (1.7e+308) in your dataset.
I think there is something wrong with your feeding declaration. Can you paste the result of a call to /api/<vizid>/feeds ?
Thanks,
Arnaud
Is it possible to use the custom element html format when viewing a report in the Webi html viewer and then substitute the custom element image format when the report is exported/viewed in PDF mode? How does one accomplish this so that one report can be used for both viewing (html format) and exporting (image format) of a custom element?
Hello Craig,
Yes, it is possible. In the CMC, set the media to text/html. Then, WebI clients will automatically request html content when displaying the custom element content in documents, while WebI server will keep requesting for bitmap content when exporting to PDF or Excel.
Whatever media you set in the CMC, WebI server will always request a bitmap content when exporting to PDF or Excel.
Regards,
Pascal.
Perfect! Thanks!
Hi,
Is there a bug that the second try call is also not POST (Apache access log):
10.1.9.157 - - [01/Aug/2016:13:02:31 +0000] "POST /custom/api/visualizations/EmailAlert/render?format=text/html&locale=en_US HTTP/1.1" 301 310
10.1.9.157 - - [01/Aug/2016:13:02:31 +0000] "GET /custom/api/visualizations/EmailAlert/render/?format=text/html&locale=en_US HTTP/1.1" 200 117
Thanks, Timo
Hello,
I don't think this call comes from Web Intelligence. Every calls to /render endpoint are made with the verb POST. GET is simply not implemented on our side.
Arnaud
Here is speedometer gauge but using PHP. The render file cannot be in /render/index.php, so file "render" needs to be able to execute PHP. Add this to httpd.conf:
<Location "/gauge/api/visualizations/gauge">
ForceType application/x-httpd-php
</Location>
FEEDS:
<?php
header("Content-type:application/json");
echo '{
"feeds": [
{
"id": "Title",
"name": "Title Text",
"description": "Title text",
"type": "dimension",
"axis": "0",
"min": "1",
"max": "1"
},
{
"id": "Reverse",
"name": "Reverse Colors (Y/N)",
"description": "Reverse Colors",
"type": "dimension",
"axis": "1",
"min": "1",
"max": "1"
},
{
"id": "Value",
"name": "Value to Display",
"description": "Value to Display",
"type": "measure",
"min": "1",
"max": "1"
},
{
"id": "Min",
"name": "Scale Minimum Value",
"description": "Scale Minimum Value",
"type": "measure",
"min": "0",
"max": "1"
},
{
"id": "Max",
"name": "Scale Maximum Value",
"description": "Scale Maximum Value",
"type": "measure",
"min": "0",
"max": "1"
},
{
"id": "Red",
"name": "Red Band Start",
"description": "Red Band Size",
"type": "measure",
"min": "0",
"max": "1"
},
{
"id": "Yellow",
"name": "Yellow Band Start",
"description": "Yellow Band Size",
"type": "measure",
"min": "0",
"max": "1"
},
{
"id": "Green",
"name": "Green Band Start",
"description": "Green Band Size",
"type": "measure",
"min": "0",
"max": "1"
},
{
"id": "Size",
"name": "Size of the Graph",
"description": "Size of Graph",
"type": "measure",
"min": "0",
"max": "1"
}]}';
?>
RENDER:
<?php
$a = file_get_contents('php://input');
header("Content-type:text/html");
$b = json_decode($a,true);
isset( $b['data']['0']['values']['rawvalues'][0]) ? $label = $b['data']['0']['values']['rawvalues'][0] : $label = '' ;
isset( $b['data']['1']['values']['rawvalues'][0]) ? $reverse = $b['data']['1']['values']['rawvalues'][0] : $reverse = 'N' ;
isset( $b['data']['2']['values']['rawvalues'][0][0]) ? $value = $b['data']['2']['values']['rawvalues'][0][0] : $value = 0 ;
isset( $b['data']['3']['values']['rawvalues'][0][0]) ? $min = $b['data']['3']['values']['rawvalues'][0][0] : $min = 0 ;
isset( $b['data']['4']['values']['rawvalues'][0][0]) ? $max = $b['data']['4']['values']['rawvalues'][0][0] : $max = 100 ;
isset( $b['data']['5']['values']['rawvalues'][0][0]) ? $red = $b['data']['5']['values']['rawvalues'][0][0] : $red = 100 ;
isset( $b['data']['6']['values']['rawvalues'][0][0]) ? $yellow = $b['data']['6']['values']['rawvalues'][0][0] : $yellow = 100 ;
isset( $b['data']['7']['values']['rawvalues'][0][0]) ? $green = $b['data']['7']['values']['rawvalues'][0][0] : $green = 100 ;
isset( $b['data']['8']['values']['rawvalues'][0][0]) ? $size = $b['data']['8']['values']['rawvalues'][0][0] : $size = '200' ;
?>
<html>
<head>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["gauge"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Label', 'Value'],
['<?php echo $label; ?>', <?php echo $value; ?>]
]);
var options = {
<?php if ($reverse=='N') { ?>
redFrom: <?php echo $red; ?>, redTo: <?php echo $yellow; ?>,
yellowFrom:<?php echo $yellow; ?>, yellowTo: <?php echo $green; ?>,
greenFrom:<?php echo $green; ?>, greenTo: <?php echo $max; ?>,
<?php } else { ?>
redFrom: <?php echo $red; ?>, redTo: <?php echo $max; ?>,
yellowFrom:<?php echo $yellow; ?>, yellowTo: <?php echo $red; ?>,
greenFrom:<?php echo $green; ?>, greenTo: <?php echo $yellow; ?>,
<?php } ?>
height: <?php echo $size; ?>,
minorTicks: 5, max: <?php echo $max; ?>, min: <?php echo $min; ?>
};
var chart = new google.visualization.Gauge(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
</head>
<body>
<div id="chart_div"></div>
</body>
</html>
Hi,
Please let me know if anyone worked on d3 visualization custom element or have brief idea about this.
Thanks,
Vivek
Hi Vivek,
Yes, we have tested some D3 visualizations, such as the famous calendar example. That works fine. Like with the Google chart sample we have documented in this article, it needs a little bit of coding for the wrapper: to list the visualization, its feeds and then to render it.
Regards,
Pascal.
Hi Pascal,
Thanks for your reply.
Could you please share some sample code for wrapping?
will this code work for Lumira CVOM extensions too?
Thanks,
Vivek
Hi Vivek,
The only code we can share, is the one in this sample. But, basically, it's very similar to the code used with a D3 visualization, or a Lumira visualization or any other kind of javascript visualization... You just need to figure out how to map the Custom Elements APIs to the visualization library you want to use.
Regards,
Pascal.
Pascal,
Have you worked with the new ability in SP3 to provide Custom Settings? I can get custom settings to show up for the user to interact with, but they are not being passed back to me in the render call (api/visualizations/<vizID>/render). The documentation says "Rendering setting values are saved in the Web Intelligence document" on page 62 of the "SAP BusinessObjects BI Developer’s Guide for Web Intelligence and the BI Semantic Layer", but that doesn't appear to be the case for me. Once I provide a value for a custom setting, it seems to disappear.
Hello Craig,
I have personally not played with the new Custom Elements settings API in Web Intelligence 4.2 SP3. However some of my colleagues did. I also know that some SAP partners are currently working with it.
I know one bug has been found with the "string" type parameter, in the dHTML client, but I have not heard of any other problem...
Regards,
Pascal.
I created a ticket with SAP support, but they are struggling to even begin to help me because they have not worked at all with Custom Elements. Could you ask your colleagues to describe the basic flow of the custom settings? As I understand it, I create a json string that defines the format for the custom settings I want. This json format is returned to Web Intelligence when the GET api/visualizations/<viz_id>/settings is invoked when a user right clicks a custom element and chooses format. From that point on, Web Intelligence server side code should handle saving the values the user provides? It should also pass those values to the POST api/visualizations/<viz_id>/render call? But Web Intelligence is doing neither. It is not saving, and it is not passing setting values back. I must be missing something?
Hi Pascal,
can I have this attachment (CustomElementsGoogleCharts.txt, couldn’t see it in this article), as I’m newbie to Java and have no idea on how to build one from scratch? and when you said testing service http://your_service:port, what did you mean, is it BO server URL or what else.
Hi Richard,
See below.
Regards,
Pascal.
Hi Pascal,
can I have the file CustomElementsGoogleCharts.txt, couldn’t see it in this article.
Thanks,
Regards
José
Hi,
As per my understanding, there are mainly two parts i.e.
1. Develop your custom element
2. Publish your custom element using NodeJS as a URL
In order to achieve the first step, is there any reference document available ? I am not getting it anywhere. Please help me to find it.
Hi Devesh,
See below.
Regards,
Pascal.
Dear all,
It seems the sample attachment has been lost during the SCN migration. I'm deeply sorry about that. Here it is again, inline :
// DISCLAIMER: The SCN, Content, and Services are being provided to You AS IS.
// To the fullest extent allowable by law, SAP does not guarantee or warrant any
// features or qualities of the SCN, Content, or Services or give any undertaking
// with regard to any other quality. Statements and explanations to SCN, Content
// or Services in promotional material or on SCN and in the documentation are
// made for explanatory purposes only; they are not meant to constitute any
// guarantee or warranty of certain features. No warranty or undertaking shall be
// implied by a User from any published SAP description of or advertisement
// except to the extent SAP has expressly confirmed such warranty or undertaking
// in writing. Warranties are validly given only with the express written
// confirmation of SAPs management.
// For a description of the Google visualizations used in this application,
// see https://developers.google.com/chart/interactive/docs/
// Initialization
var express = require('express');
var bodyParser = require('body-parser')
var path = require('path');
var phantomProxy = require('phantom-proxy');
var fs = require('fs');
var util = require('util');
var app = express();
app.use(bodyParser.json());
var urlencodedParser = bodyParser.urlencoded({ extended: true });
var CR = String.fromCharCode(13);
// Set the port number
app.listen(port);
app.get("/", function(req, res) {
res.send("Server Up and Running !");
});
app.get("/api/about", function(req, res) {
logInfo("About");
res.send("Google Visualizations");
});
app.get("/api/formats", function(req, res) {
logInfo("Start Format");
var format = {"formats": ["text/html", "image/png"]};
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(format));
logInfo("Stop Format");
});
app.get("/api/visualizations", function(req, res) {
logInfo("Start Visualizations");
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(factory));
logInfo("Stop Visualizations");
});
app.get("/api/visualizations/:vizid/feeds", function(req, res) {
logInfo("Start feeds");
var viz = factory.getViz(req.params.vizid);
logInfo(" Viz ID => " + viz.name);
var feedArray = [];
if (!(viz.categoryAxisMin == 0 && viz.categoryAxisMax == 0)) {
feedArray.push(
{
"id": "category-axis",
"name": viz.categoryAxisName,
"description": viz.categoryAxisDesc,
"axis": "0",
"type": "dimension",
"min": ""+viz.categoryAxisMin+"",
"max": ""+viz.categoryAxisMax+""
}
);
}
if (!(viz.regionColorMin == 0 && viz.regionColorMax == 0)) {
feedArray.push(
{
"id": "region-color",
"name": viz.regionColorName,
"description": viz.regionColorDesc,
"axis": "1",
"type": "dimension",
"min": ""+viz.regionColorMin+"",
"max": ""+viz.regionColorMax+""
}
);
}
if (!(viz.primaryValueMin == 0 && viz.primaryValueMax == 0)) {
feedArray.push(
{
"id": "primary-values",
"name": viz.primaryValueName,
"description": viz.primaryValueDesc,
"type": "measure",
"min": ""+viz.primaryValueMin+"",
"max": ""+viz.primaryValueMax+""
}
);
}
var feeds = {
"feeds": feedArray
};
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(feeds));
logInfo("Stop feeds");
});
app.post("/api/visualizations/:vizid/render", function(req, res) {
logInfo("Start Render");
var viz = factory.getViz(req.params.vizid);
var html = viz.generate(req);
var height = req.body.height;
var width = req.body.width;
var format = req.headers.accept;
logInfo(" Viz ID => "+viz);
// Bitmap generation
if(format == "image/png") {
var self = this;
var randomnumber=Math.floor(Math.random()*101)
var path = 'html_page_'+randomnumber+'.html';
fs.writeFileSync(path, html);
phantomProxy.create(function(proxy) {
proxy.page.open(path, function (result) {
proxy.page.set('viewportSize', { width: width, height: height });
proxy.page.set('clipRect', { top: 0, left: 0, width: width, height: height });
proxy.page.waitForSelector("#chart", function (result){
proxy.page.renderBase64('PNG', function(result){
var bitmap = new Buffer(result, 'base64');
res.writeHead(200, {'Content-Type': 'image/png' });
res.end(bitmap, 'binary');
phantomProxy.end();
fs.unlinkSync(path); // Delete temp file
});
});
});
});
}
// HTML generation
else {
res.writeHead(200, {'Content-Type': 'text/html' });
res.end(html);
}
logInfo("Stop Render");
});
var VizFactory = function() {
this.visualizations = [];
this.getViz = function(id) {
for (var index = 0 ; index < this.visualizations.length ; index++) {
if (this.visualizations[index].id === id) return this.visualizations[index];
}
};
};
var Visualization = function(id, name, catMin, catMax, regMin, regMax, valMin, valMax, catName, catDesc, regName, regDesc, valName, valDesc) {
this.id = id;
this.name = name;
this.categoryAxisMin = catMin;
this.categoryAxisMax = catMax;
this.categoryAxisName = catName;
this.categoryAxisDesc = catDesc;
this.regionColorMin = regMin;
this.regionColorMax = regMax;
this.regionColorName = regName;
this.regionColorDesc = regDesc;
this.primaryValueMin = valMin;
this.primaryValueMax = valMax;
this.primaryValueName = valName;
this.primaryValueDesc = valDesc;
};
/*******************
* Google Pie Chart
*
* 1: ID
* 2: Name
* 3: Category feeds min = 1
* 4: Category feeds max = 1
* 5: Region feeds min = 0 (optional)
* 6: Region feeds max = 1
* 7: Value feeds min = 1
* 8: Value feeds max = 1
* 9: Category feeds name
* 10: Category feeds description
* 11: Region feeds name
* 12: Region feeds description
* 13: Value feeds name
* 14: Value feeds description
*/
var vizPie = new Visualization("googlepie", "Google Pie", 1, 1, 0, 1, 1, 1, "Sectors Dimension", "Dimension on which the pie will be splitted", "Treillis Dimension", "Dimension to repeat the pies", "Measure", "Measure values");
vizPie.generate = function(req) {
var w = req.body.width?req.body.width:800;
var h = req.body.height?req.body.height:800;
var hCell = h;
var dimData = getData(req, getFeedings(req, "category-axis")[0].dataId);
var regFeed = getFeedings(req, "region-color");
var mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
var count0 = 1;
var count1 = 1;
var values = [];
var value = [];
if (regFeed) {
if (regFeed.length === 1) {
var regData0 = getData(req, regFeed[0].dataId);
count0 = regData0.values.rawvalues.length;
for (var j = 0 ; j < count0 ; j++) {
value = [];
var reg = regData0.values.rawvalues[j];
for (var i = 0 ; i < dimData.values.rawvalues.length ; i++) {
var dim = dimData.values.rawvalues[i];
var mes = mesData.values.rawvalues[j][i];
if (mes == 1.7e+308) mes = 0; // 1.7e+308 = WebI overflow
value.push([dim.toString(), mes]);
}
values.push(value);
}
} else {
var regData0 = getData(req, regFeed[0].dataId);
var regData1 = getData(req, regFeed[1].dataId);
var regValues0 = distinct(regData0.values.rawvalues);
var regValues1 = distinct(regData1.values.rawvalues);
count0 = regValues0.length;
count1 = regValues1.length;
hCell = Math.floor(h/count1);
var allData = {};
for (var i = 0 ; i < mesData.values.rawvalues.length ; i++) {
if (!allData[regData0.values.rawvalues[i]]) allData[regData0.values.rawvalues[i]] = {};
allData[regData0.values.rawvalues[i]][regData1.values.rawvalues[i]] = mesData.values.rawvalues[i];
}
for (var j = 0 ; j < count1 ; j++) {
for (var i = 0 ; i < count0 ; i++) {
var k = i + count0*j;
value = [];
if (allData[regValues0[i]] && allData[regValues0[i]][regValues1[j]]) {
var m = allData[regValues0[i]][regValues1[j]];
for (var l = 0 ; l < dimData.values.rawvalues.length ; l++) {
var dim = dimData.values.rawvalues[l];
var mes = m[l];
if (mes == 1.7e+308) mes = 0;
value.push([dim.toString(), mes]);
}
}
values.push(value);
}
}
}
} else {
for (var i = 0 ; i < dimData.values.rawvalues.length ; i++) {
var dim = dimData.values.rawvalues[i];
var mes = mesData.values.rawvalues[i];
if (mes == 1.7e+308) mes = 0;
value.push([dim.toString(), mes]);
}
values.push(value);
}
var html =
'<!DOCTYPE html>' + CR +
'<html style="height:100%; display:table">' + CR +
' <head>' + CR +
' <script type="text/javascript" src="https://www.google.com/jsapi"></script>' + CR +
' <script type="text/javascript">' + CR +
' google.load("visualization", "1.0", {"packages":["corechart"]});' + CR +
' google.setOnLoadCallback(drawChart);' + CR +
' function drawChart() {' + CR +
' var data, chart;' + CR;
for (var j = 0 ; j < count1 ; j++) {
for (var i = 0 ; i < count0 ; i++) {
var k = j + count1 * i;
html +=
' data = new google.visualization.DataTable();' + CR +
' data.addColumn("string", "' + dimData.title + '");' + CR +
' data.addColumn("number", "' + mesData.title + '");' + CR +
' data.addRows(' + JSON.stringify(values[k]) + ');' + CR +
' var options = {' + CR;
if (count1 > 100) {
html += ' title: "' + regValues1[j] + ' / ' + regValues0[i] + '",' + CR;
} else if (count0 > 100) {
html += ' title: "' + regData0.values.rawvalues[k] + '",' + CR;
}
html +=
' backgroundColor: "transparent",' + CR +
' legend: "none"' + CR +
' };' + CR +
' new google.visualization.PieChart(document.getElementById("chart_cell_' + j + '_' + i + '")).draw(data, options);;' + CR;
}
}
html +=
' }' + CR +
' </script>' + CR +
' </head>' + CR +
' <body style="border:0; margin:0; padding:0; height:100%; vertical-align:middle; display:table-cell">' + CR +
' <table style="border:0; margin:0; padding:0; width:100%; height:100%; table-layout:fixed">' + CR;
for (var j = 0 ; j < count1 ; j++) {
html += ' <tr style="border:0; margin:0; padding:0">' + CR;
for (var i = 0 ; i < count0 ; i++) {
var k = i + i*j;
html += ' <td style="border:0; margin:0; padding:0; height:' + hCell + 'px" id="chart_cell_' + j + '_' + i + '"></td>' + CR;
}
html += ' </tr>' + CR;
}
html +=
' </table>' + CR +
' </body>' + CR +
'</html>' + CR;
return html;
};
/*********************
* Google Gauge Chart
*
* 1: ID
* 2: Name
* 3: Category feeds min = 1
* 4: Category feeds max = 1
* 5: Region feeds min = 0 (unused)
* 6: Region feeds max = 0 (unused)
* 7: Value feeds min = 1
* 8: Value feeds max = 1
* 9: Category feeds name
* 10: Category feeds description
* 11: Region feeds name
* 12: Region feeds description
* 13: Value feeds name
* 14: Value feeds description
*/
var vizGauge = new Visualization("googlegauge", "Google Gauge", 1, 1, 0, 0, 1, 1, "Gauge Dimension", "Dimension values to repeat the gauges", "N/A", "N/A", "Measure", "Gauge value (should be 1 to 100)")
vizGauge.generate = function(req) {
var w = req.body.width?req.body.width:800;
var h = req.body.height?req.body.height:800;
var dimData = getData(req, getFeedings(req, "category-axis")[0].dataId);
var mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
var values = [['Label', 'Value']];
var RenderingValues = [];
for (var i = 0 ; i < dimData.values.rawvalues.length ; i++) {
values.push([dimData.values.rawvalues[i], 0]);
RenderingValues.push(mesData.values.rawvalues[i]);
}
var html =
'<!DOCTYPE html>' + CR +
'<html style="height:100%; margin:auto; display:table">' + CR +
' <head>' + CR +
' <script type="text/javascript" src="https://www.google.com/jsapi"></script>' + CR +
' <script type="text/javascript">' + CR +
' google.load("visualization", "1", {packages:["gauge"]});' + CR +
' google.setOnLoadCallback(drawChart);' + CR +
' function drawChart() {' + CR +
' var data = google.visualization.arrayToDataTable(' + CR +
' ' + JSON.stringify(values) + CR +
' );' + CR +
' var options = {' + CR +
' width:' + w + ',' + CR +
' height:' + h + ',' + CR +
' greenFrom: 0,' + CR +
' greenTo: 20,' + CR +
' redFrom: 90,' + CR +
' redTo: 100,' + CR +
' yellowFrom:75,' + CR +
' yellowTo: 90,' + CR +
' minorTicks: 5,' + CR +
' animation : {' + CR +
' duration: 2500,' + CR +
' easing: "out"' + CR +
' }' + CR +
' };' + CR +
' var chart = new google.visualization.Gauge(document.getElementById("chart_div"));' + CR +
' chart.draw(data, options);' + CR +
' setInterval(function() { '+ CR;
for(var j = 0;j < values.length-1 ; j++){
html += 'data.setValue('+j+',1,'+ RenderingValues[j] +');' + CR;
}
html +=
' chart.draw(data,options);' + CR +
' }, 1000);' + CR +
' }' + CR +
' </script>' + CR +
' </head>' + CR +
' <body style="border:0; margin:0; padding:0; height:100%; vertical-align:middle; display:table-cell">' + CR +
' <div style="border:0; margin:0; padding:0" id="chart_div" onmouseover="drawChart();"></div>' + CR +
' </body>' + CR +
'</html>' + CR;
return html;
};
/************************
* Google Sankey Diagram
*
* 1: ID
* 2: Name
* 3: Category feeds min = 2
* 4: Category feeds max = 2
* 5: Region feeds min = 0 (unused)
* 6: Region feeds max = 0 (unused)
* 7: Value feeds min = 1
* 8: Value feeds max = 1
* 9: Category feeds name
* 10: Category feeds description
* 11: Region feeds name
* 12: Region feeds description
* 13: Value feeds name
* 14: Value feeds description
*/
var vizSankey = new Visualization("googlesankey", "Google Sankey", 2, 2, 0, 0, 1, 1, "Source & Destination Dimensions", "Source and destination dimensions", "N/A", "N/A", "Measure", "Measure values")
vizSankey.generate = function(req) {
var w = req.body.width?req.body.width:1100;
var h = req.body.height?req.body.height:900;
var dimCount = getFeedings(req,"category-axis").length;
var mesCount;
var values = [];
var V = [];
var mesData;
var FromDimId, FromDimData, ToDimId, ToDimData;
if(getFeedings(req,"primary-values")){
mesCount = getFeedings(req,"primary-values").length;
} else {
mesCount = 0;
}
for(var j = 0; j < dimCount-1; j++){
if(mesCount == 1){
mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
} else if(mesCount == dimCount-1){
mesData = getData(req, getFeedings(req, "primary-values")[j].dataId);
}
FromDimId = getFeedings(req, "category-axis")[j].dataId;
ToDimId = getFeedings(req, "category-axis")[j+1].dataId;
FromDimData = getData(req, FromDimId );
ToDimData = getData(req, ToDimId);
for (var i = 0 ; i < FromDimData.values.rawvalues.length; i++) {
var From = FromDimData.values.rawvalues[i];
var To = ToDimData.values.rawvalues[i];
if(From == To){
To += ' ' + ToDimData.title;
ToDimData.values.rawvalues[i] = To;
}
if(!V[From]){
V[From]= {};
}
if(!V[From][To]){
V[From][To]= 0;
}
if(!mesCount){
V[From][To] += 1; // =1 if no ponderation desired
} else {
V[From][To] += mesData.values.rawvalues[i];
}
}
FromDimLov = distinct(FromDimData.values.rawvalues);
ToDimLov = distinct(ToDimData.values.rawvalues);
for(var k = 0; k < FromDimLov.length;k++){
for(var l = 0; l < ToDimLov.length; l++){
if(V[FromDimLov[k]][ToDimLov[l]]){
values.push([FromDimLov[k],ToDimLov[l],V[FromDimLov[k]][ToDimLov[l]]]);
}
}
}
}
var html =
'<!DOCTYPE html>' + CR +
'<html style="height:100%; margin:auto; display:table">' + CR +
' <head>' + CR +
' <script type="text/javascript" src="https://www.google.com/jsapi"></script>' + CR +
' <script type="text/javascript">' + CR +
' google.load("visualization", "1.1", {packages:["sankey"]});' + CR +
' google.setOnLoadCallback(drawChart);' + CR +
' function drawChart() {' + CR +
' var data = new google.visualization.DataTable();' + CR +
' data.addColumn(\'string\', \'From\');' + CR +
' data.addColumn(\'string\', \'To\');' + CR +
' data.addColumn(\'number\', \'Weight\');' + CR;
for(var n = 0; n < values.length ; n++){
html +=
' data.addRows([' + CR +
' [ "'+ values[n][0] +'", "'+ values[n][1] +'", '+ values[n][2] +' ]' + CR +
' ]);' + CR;
}
html +=
' // Sets chart options.' + CR +
' var options = {' + CR +
' width: ' + w +',' + CR +
' height: ' + h + ',' + CR +
' };' + CR +
' // Instantiates and draws our chart, passing in some options.' + CR +
' var chart = new google.visualization.Sankey(document.getElementById(\'sankey_basic\'));' + CR +
' chart.draw(data, options);' + CR +
' }' + CR +
'</script>' + CR +
'</head>' + CR +
' <body style="border:0; margin:0; padding:0; height:100%; vertical-align:middle; display:table-cell">' + CR +
' <div style="border:0; margin:0; padding:0" id="sankey_basic"></div>' + CR +
' </body>' + CR +
'</html>' + CR;
return html;
}
var factory = new VizFactory();
factory.visualizations.push(vizGauge);
factory.visualizations.push(vizPie);
factory.visualizations.push(vizSankey);
function getFeedings(req, id) {
for (var i = 0 ; i < req.body.feeding.length ; i++) {
if (req.body.feeding[i].id === id) {
return req.body.feeding[i].expressions;
}
}
};
function getData(req, id) {
for (var i = 0 ; i < req.body.data.length ; i++) {
if (req.body.data[i].id == id) {
return req.body.data[i];
}
}
};
function distinct(arr) {
var values = [];
for (var i = 0 ; i < arr.length ; i++) {
var value = arr[i];
if (values.indexOf(value) === -1) {
values.push(value);
}
}
return values;
};
function logInfo(info){
var currentdate = new Date();
console.log(currentdate.toLocaleString() + " => " + info);
};
When using the Google gauge extension, is it possible to print it to a PDF file?
Hi,
It is up to the Custom Elements service to implement the bitmap generation required for printing and publishing.
Regards,
Pascal.
Hi Pascal,
I didn't find the file (CustomElementsGoogleCharts.txt) attached to this article.
can you send it to me at asy041atgmail
Hi All,
I have few queries :
1.We do not have internet access on our servers.So what needs to be mentioned for proxy “http://your_proxy:port“
2.How can we get below packages installed
npm install phantom-proxy (necessary to use phantomJS from nodeJS)
npm install xmldoc (necessary to parse the XML code)
npm install body-parser (necessary to parse the commands from Web Intelligence)
npm install pm2 –g (PM2 is a NodeJS process manager and is mandatory to manage your Custom Elements service)
3.The above setting are for windows server can we work with Linux servers.
Regards.
this step is not working for me, can you please let me know what would be the service name and port that should be mentioned here
Hi all,
is there anybody who have file CustomElementsGoogleCharts.txt ?
because I copy/paste what Pascal posted here, but when I want to start by :
pm2 start CustomElementsGoogleCharts.js
the service failed immediately on error
🙁
Hi Pierrick,
I had the same problem with copy/paste.
In C:\Users\<user_name>\.pm2\logs I found the CustomElementsGoogleCharts-error-0.log file with this error:
So the issue is the quote (and also the double quote) symbol. Replace in the file the
with
and
with
to solve the problem (you must replace opened/closed quotes and double quotes).
Regards,
Denis.
Hi Denis,
thank you very much it is working now.
I was able to add the custom service in the CMC.
In WebI document I see as custom extension Google Gauge, Google Pie and Google Sankey , but only Google Pie is working :-/
If you have any clue !
Thanks a lot
Hi Pierrick,
yeah not all charts work, also for me the same issue. I used this example to understand the base logic to pass the parameters from Webi to .js file, then I have created my custom chart writing a bit of javascript language and using also D3.js. In any case I will try to fine where is the problem….
Ok, try this piece of code for Gauge (replace the current generate function with this):
vizGauge.generate = function(req) {
var w = req.body.width ? req.body.width : 800;
var h = req.body.height ? req.body.height : 800;
var dimData = getData(req, getFeedings(req, "category-axis")[0].dataId);
var mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
var values = [
['Label', 'Value']
];
for (var i = 0; i < dimData.values.rawvalues.length; i++) {
values.push([dimData.values.rawvalues[i], mesData.values.rawvalues[i]]);
}
var html =
'<!DOCTYPE html>' + CR +
'<html>' + CR +
'<head>' + CR +
' <script src="https://www.gstatic.com/charts/loader.js"></script>' + CR +
' <script type="text/javascript">' + CR +
'google.charts.load(\'current\', {\'packages\':[\'gauge\']});' + CR +
' google.charts.setOnLoadCallback(drawChart);' + CR +
' function drawChart() {' + CR +
' var data = google.visualization.arrayToDataTable(' + CR +
' ' + JSON.stringify(values) + CR +
' );' + CR +
' var options = {' + CR +
' width:' + w + ',' + CR +
' height:' + h + ',' + CR +
' greenFrom: 0,' + CR +
' greenTo: 20,' + CR +
' redFrom: 90,' + CR +
' redTo: 100,' + CR +
' yellowFrom:75,' + CR +
' yellowTo: 90,' + CR +
' minorTicks: 5,' + CR +
' animation : {' + CR +
' duration: 2500,' + CR +
' easing: "out"' + CR +
' }' + CR +
' };' + CR +
' var chart = new google.visualization.Gauge(document.getElementById(\'chart_div\'));' + CR +
' chart.draw(data, options);' + CR +
' }' + CR +
' </script>' + CR +
'</head>' + CR +
'<body>' + CR +
' <div id="chart_div" style="width: 400px; height: 120px;"></div>' + CR +
'</body>' + CR +
'</html>' + CR;
return html;
};
Regards,
Denis.
Hi, I'm trying to have it working. I went through all the steps described so far, but as soon as I try to start the service, this fails with the following log lines:
Error: Cannot find module 'phantom-proxy'
at Function.Module._resolveFilename (module.js:538:15)
at Function.Module._load (module.js:468:25)
........
I double checked for "phantom-proxy" install by running npm list, and what I get is:
...............
│ ├── wrappy@1.0.1
│ └── write-file-atomic@1.1.3
├─┬ phantom-proxy@0.1.792
│ ├── colors@0.6.2
│ ├─┬ event-stream@3.0.20
......
Did anybody hit the head against this?
Thank you so much in advance
Claudio
Hi ,
I am able to create Custom Element service and add that service in CMC. Its Test is successful. But when I assign data to any charts in custom element of BI 4.2 , it is not working.
Please suggest.
Thanks
Shilpi
Hi,
I have my CustomElementsGoogleCharts.js started on pm2.
But when I try to create the service in the CMC : the test is not valid.
I try this :
http://BOServer/CustomElementsGoogleCharts:80
And I try this :
http://BOServer/CustomElementsGoogleCharts:80/Google Gauge (for example…)
I have this message :
Le test a échoué.
java.net.ConnectException: Connection refused: connect
I tried another thing :
http://BOServer:BOPort/CustomElementsGoogleCharts:80
Le test a échoué.
http://BOserver:BOPort/CustomElementsGoogleCharts:80/api/formats
and when i try :
http://BOServer:BOPort/CustomElementsGoogleCharts:80/Google Gauge
I have only :
Le test a échoué.
What’s my problem ? Can you help me ?
Thanks.
Ludéric
Hi Luderic,
It's difficult to answer your question without knowing where your Custom element service has been deployed. Are you sure about the path and the port number?
In our example, the correct URL is: http://mymachine:8095 where "mymachine" is the machine where NodeJS has been installed and the service is running
Thanks,
Pascal.
Hi Pascal,
First : Thanks for this blog !
and thanks for answering me.
Edit : Ok : the http://mymachine:Port works !!
Thanks a lot.
Ludéric
Hi Ludéric,
The port to use in the URL is the one defined in the JS code. Using the port 80 is a bit risky since this is a generic HTTP port. Try with another port number, such as the one given in our example, above.
Also, in the URL, there should not be any path, like "/CustomElementsGoogleCharts", if the JS code is at the root of the nodeJS folder.
Thanks,
Pascal.
Hi,
Is it possible to download the file "CustomElementsGoogleCharts.txt" you mentionned ? Where I can find the content of this file ? Thank you.
Regards.
Hi William,
Sorry, this blogging application does not allow to attach files, as the old SCN application used to... 🙁
I copied the text inline, above https://blogs.sap.com/2016/03/15/how-to-build-a-custom-elements-service-for-sap-web-intelligence-42/comment-page-1/#comment-357729
That's the only workaround I could find. Sorry about that...
Pascal.
This article was very helpful in getting me started down the route of custom elements. After a few run-ins with issues, was able to finally get a custom element visualization using leaflet and some custom json map layers.. works beautifully with input controls as well. A real game-changer for webi now that any visualization is within scope of webi. Kudos to you and the custom elements team.
I am getting some errors with getting the CMC to work with custom elements using HTTPS.
HTTP works though, just not HTTPS.
Is HTTPS even supported for Custom elements calls ?
Hi,
Yes, HTTPS is supported.
Thanks,
Pascal.
Hi,
Do you have any documentation that explains how to do that.
Because, when I changed my HTTP Custom element service to be served over HTTPS, CMC is able to test it, but in WebI and Rich Client it fails with error:
An exception occurred when retrieving the feeding metadata from the CustomElement third party service. (Error: RWI 00634) (Error: INF )
Hi,
No, there isn't any documentation on HTTPS, since there's nothing specific to do with the Custom Element service setting.
The exception you get seems to indicate there is a communication issue between the Custom Element service and the Web Intelligence client.
Regards,
Pascal.
I can confirm that changing the service to HTTPS causes the problem. I too am getting the same error. Since it is already more than 2 years, do you plan to provide any updates on this?
Hi Pascal,
Wanted to let you know that I integrated leaflet maps into webi and i can do visualizations like this that was not possible before. This one takes it up a notch for sure.
Thanks for all your support on this forum and to the team for implementing custom elements feature.