Friday, November 16, 2018

OBIEE 12c - Enable Copying from Reports / Analysis

OBIEE 12c like its predecessor OBIEE 11g is not very good in simple copy / paste functionality for copying Data from its Reports and Analysis. It does allow you to copy / paste Cell Values or Multiple Cells in Vertical Range (refer here), it does not really allows you to copy them 'freely' like how you could do it on any page on Internet.

So, here is an article on how you could twist around some JavaScript files in OBIEE system to enable a much simpler copy / paste. End result would be a Context Menu in OBIEE which shows up when you right click on any Analysis Results. It would give you two options - 1) Copy the Cell Value OR 2) Copy the Visible Table (in a New Window)

End Result Copy Functionality in OBIEE 12c

Warning!! Before I begin - I have to put forward this little note that, following is something which is not for a faint hearted person or a newbie. You should have a fair understanding of Java Scripts, JARs, Weblogic, Custom Deployments and of course of the OBIEE itself. If you do not have enough working experience with these and you break something on your end while following below guide, I would not be responsible for it. Always ensure you take backup of relevant files so you could restore them later.

Also, much of the ground work for this was already done and documented by my good Russian friend Sergey at Obi2ru.blogspot.com (here's to you Sergey!!) later translated in English and refined further by Tobias at Virtual7. You could refer there work here and here. All credit goes to them. There work was for OBIEE 11g, I have updated this Blog post for OBIEE 12c.

And here we begin...

You will need to modify below three files:
  1. viewrightclickmenu.js
  2. obips.viewmodel.js
  3. (pivot\) pivotview.js

If you are familiar with 11g file locations, you could easily find these files in ORACLE_HOME\bifoundation\web\appv2\res\b_mozilla\views directory in your install path. In 12c though, these are compressed and available inside BI_ORACLE_HOME\bifoundation\jee\analytics.ear file. 
(Look here for location of different home directories, if you are unaware about them)

In case you have deployed a Custom Style using Shared Folder to your OBIEE 12c installation (which is a good thing!!) then you are in luck and things would be easier for you going down the road. As you could directly change above files in the Custom Shared Folder instead of updating these in analytics.ear file (Also to mention here is the risk of losing the changes after analytics.ear file is replaced during system patching). I highly recommend the Shared Folder approach and in case you wish to do it follow Gianni Ceresa's article here before following the rest of this guide.


So once you have identified how to get the above three files, modify them as below.

(I also highly recommend to Pretty Format the above JavaScript files before editing to make editing easier - use websites like jsbeautifier.org for this)

File: viewrightclickmenu.js


Locate object obips.ViewRightClickMenu.prototype.onEdgeContextMenu in the script. This is around line 40 in the file. Locate below lines further after this object:

var F = "";
var k = g.isLayerHierarchical(h, H);
var u = obips.ViewRightClickMenu.isRuntimeMode();

After these lines add the below code and Save the file.

// Copy Functionality Change - Start
labelText = "Copy Cell Value...";
buttonEvent = obips.ViewModel.EventType.CF_COPY_CELL_VALUE;
var p = obips.ViewRightClickMenu.CreateContextMenuOption(labelText, labelText, null, null, buttonEvent, f);
i.push(p);
labelText = "Copy Visible Table... (New Window)";
buttonEvent = obips.ViewModel.EventType.CF_COPY_TABLE_HTML;
var q = obips.ViewRightClickMenu.CreateContextMenuOption(labelText, labelText, null, null, buttonEvent, f);
i.push(q);
// Copy Functionality Changed - End

File: obips.viewmodel.js

Find variable obips.ViewModel.EventType.SHOW_SORT_DIALOG = 54; This is around line 1935 in the file.

Add below lines after the above variable declaration.

// Copy Functionality Change - Start
obips.ViewModel.EventType.CF_COPY_CELL_VALUE = 100;
obips.ViewModel.EventType.CF_COPY_TABLE_HTML = 101;
// Copy Functionality Changed - End

Now find the below case statement condition. This is around line 2100.

case obips.ViewModel.EventType.SHOW_SORT_DIALOG:
obips.ViewModel.openSortDialogHandler(d);
break;

Add below two case conditions after the above conditions.

// Copy Functionality Change - Start
case obips.ViewModel.EventType.CF_COPY_CELL_VALUE:  
var b = obips.EdgeCoords.findCoords(d.getElement());
var a = b.getId();
var f = b.getEdge();
var d = b.getLayer();
var g = b.getSlice();
if (g == -1) {
var eId = "db_" + a + "_" + f + "_" + d;
} else {
var eId = "e_" + a + "_" + f + "_" + d + "_" + g;}
var cell = document.getElementById(eId);
var cellValue = cell.innerHTML;
if (cellValue) {
cellValue = cellValue.toString().replace(/\s*\<.*?\>\s*/g, '');
cellValue = cellValue.toString().replace(/&lt;/g, '<');
cellValue = cellValue.toString().replace(/&gt;/g, '>');
cellValue = cellValue.toString().replace(/&quot;/g, '"');
cellValue = cellValue.toString().replace(/&nbsp;/g, ' ');
}
window.prompt("Press \'Ctrl + C\' on your keyboard to Copy Cell value. Press OK or Cancel to dismiss this Prompt.", cellValue );
break;
case obips.ViewModel.EventType.CF_COPY_TABLE_HTML:
var b = obips.EdgeCoords.findCoords(d.getElement());
var a = b.getId();
var f = b.getEdge();
var d = b.getLayer();
var g = b.getSlice();
if (g == -1) {
var eId = "db_" + a + "_" + f + "_" + d;
} else {
var eId = "e_" + a + "_" + f + "_" + d + "_" + g;}
var cell = document.getElementById(eId);
var cellTable = cell.parentNode;
while (cellTable) {
if (cellTable.tagName.toLowerCase() == 'table') {
break;
} else {
cellTable = cellTable.parentNode;
}
}
function OpenCopyWindow(CSSText, BodyText) {
var copyWindow = window.open("", "Copy Window" + Date().toString(), "width=" + (window.screen.availWidth*0.80) + ",height=" + (window.screen.availHeight*0.80) + "top=" + (window.screen.availHeight*0.10) + ",left=" + (window.screen.availWidth*0.10) + ",scrollbars,resizable,replace=true");
if (copyWindow == null || typeof(copyWindow) =="undefined"){
alert("Please disable your Popup blocker and try again to open new Copy Window!!");
} else {
copyWindow.focus();
}
var copyWindowHTMLHead = "<title>OBIEE Copy Window</title><style>" + CSSText + "</style>";
var copyWindowHTMLBody = BodyText;
copyWindow.document.head.innerHTML = copyWindowHTMLHead;
copyWindow.document.body.innerHTML = copyWindowHTMLBody;
}
function GetCSSFunc(CSSURL, BodyText) {
var GetCSSXHR = new XMLHttpRequest();
GetCSSXHR.onreadystatechange = function() {
if(this.readyState == 4 && this.status == 200 && this.responseText) {
console.log('XHR > Request succeeded for ' + CSSURL);
this.onreadystatechange = null;
var CSSXHRSuccess = GetCSSXHR.responseText;
OpenCopyWindow(CSSXHRSuccess, BodyText);
}
else if (this.status == 400) {
console.log('XHR > failed for ' + CSSURL);
var CSSXHRError = " body {font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: medium;} table, td {border: 1px solid black; border-collapse: collapse;}";
OpenCopyWindow(CSSXHRError, BodyText);
}
}
GetCSSXHR.open("GET", CSSURL, true);
GetCSSXHR.send();
}
window.alert("In the new opened Window, Press \'Ctrl + A\' and \'Ctrl + C\' to Select All and Copy the entire Content.");
GetCSSFunc(
"/analyticsRes/res/s_DATAlysis/master/master.css"
, cellTable.outerHTML); break; // Copy Functionality Changed - End

If you are familiar with JavaScript most of the above code should be self explanatory. Basically what we are trying to do with this, is locate the specific HTML table cell on which the right click was made and then show a Message Box (Prompt) to allow copying of the value of that cell OR in second case open a new window to allow replication of the visible HTML table from the main page.

You may also notice the XHR loading of master.css file, highlighted in yellow above. You need to update the Location-URI of this file in above code as required. The reason we are loading this is to get the basic CSS for styling the table in the new opened window. I have used my Custom Style Shared Folder path; you could refer the path for this file from your Custom Style folder or default analytics/res/b_mozilla folder (as inside analytics.ear file). If this path is wrong and the XHR fails to load the file, the new opened window will not be style formatted and would look very basic. (This should not be a big problem!!)

Save and Close the file.

File: (pivot\) pivotview.js

This file is present in pivot folder inside the views folder in the same path as for above files.

Locate below case statement condition. This is around line 3200 in the file.

case obips.ViewModel.EventType.RUNNING_SUM_COLUMN_MENU_EVENT:
obips.ViewModel.setToggleRunning(c, b.columnID);
break;

After the above case condition, add the below two conditions.

// Copy Functionality Change - Start
case obips.ViewModel.EventType.CF_COPY_CELL_VALUE:  
var a = c.getId();
var e = c.getRow();
var h = c.getCol();
var eId = "db_" + a + "_" + e + "_" + h;
var cell = document.getElementById(eId);
var cellValue = cell.innerHTML;
if (cellValue) {
cellValue = cellValue.toString().replace(/\s*\<.*?\>\s*/g, '');
cellValue = cellValue.toString().replace(/&lt;/g, '<');
cellValue = cellValue.toString().replace(/&gt;/g, '>');
cellValue = cellValue.toString().replace(/&quot;/g, '"');
cellValue = cellValue.toString().replace(/&nbsp;/g, ' ');
}
window.prompt("Press \'Ctrl + C\' on your keyboard to Copy Cell value. Press OK or Cancel to dismiss this Prompt.", cellValue );
break;
case obips.ViewModel.EventType.CF_COPY_TABLE_HTML:  
var a = c.getId();
var e = c.getRow();
var h = c.getCol();
var eId = "db_" + a + "_" + e + "_" + h;
var cell = document.getElementById(eId);
var cellTable = cell.parentNode;
while (cellTable) {
if (cellTable.tagName.toLowerCase() == 'table') {
break;
} else {
cellTable = cellTable.parentNode;
}
}
function OpenCopyWindow(CSSText, BodyText) {
var copyWindow = window.open("", "Copy Window" + Date().toString(), "width=" + (window.screen.availWidth*0.80) + ",height=" + (window.screen.availHeight*0.80) + "top=" + (window.screen.availHeight*0.10) + ",left=" + (window.screen.availWidth*0.10) + ",scrollbars,resizable,replace=true");
if (copyWindow == null || typeof(copyWindow) =="undefined"){
alert("Please disable your Popup blocker and try again to open new Copy Window!!");
} else {
copyWindow.focus();
}
var copyWindowHTMLHead = "<title>OBIEE Copy Window</title><style>" + CSSText + "</style>";
var copyWindowHTMLBody = BodyText;
copyWindow.document.head.innerHTML = copyWindowHTMLHead;
copyWindow.document.body.innerHTML = copyWindowHTMLBody;
}
function GetCSSFunc(CSSURL, BodyText) {
var GetCSSXHR = new XMLHttpRequest();
GetCSSXHR.onreadystatechange = function() {
if(this.readyState == 4 && this.status == 200 && this.responseText) {
console.log('XHR > Request succeeded for ' + CSSURL);
this.onreadystatechange = null;
var CSSXHRSuccess = GetCSSXHR.responseText;
OpenCopyWindow(CSSXHRSuccess, BodyText);
}
else if (this.status == 400) {
console.log('XHR > failed for ' + CSSURL);
var CSSXHRError = " body {font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: medium;} table, td {border: 1px solid black; border-collapse: collapse;}";
OpenCopyWindow(CSSXHRError, BodyText);
}
}
GetCSSXHR.open("GET", CSSURL, true);
GetCSSXHR.send();
}
window.alert("In the new opened Window, Press \'Ctrl + A\' and \'Ctrl + C\' to Select All and Copy the entire Content.");
GetCSSFunc(
"/analyticsRes/res/s_DATAlysis/master/master.css"
, cellTable.outerHTML); break; // Copy Functionality Changed - End

Notice again the path for master.css and update it as required.

Now locate the object obips.PivotTable.getDatabodyMenuOptions. This is around line 3365. Locate below lines further after this object.

p = new ActionLinksModel(z, G, obips.JSDataLayout.DATA_EDGE, H, E, false, z.actionLinksXmlId);
p.setDataValue(u, s);
a = new ActionLinksRenderer(p, F);

After these lines add the below code and Save the file.

// Copy Functionality Change - Start
linkText = "Copy Cell Value...";
k = obips.ViewModel.EventType.CF_COPY_CELL_VALUE;
var L = obips.PivotTable.CreateContextMenuOption(linkText, linkText, null, null, k, f);
i.push(L);
linkText = "Copy Visible Table... (New Window)";
k = obips.ViewModel.EventType.CF_COPY_TABLE_HTML;
var M = obips.PivotTable.CreateContextMenuOption(linkText, linkText, null, null, k, f);
i.push(M);
// Copy Functionality Changed - End

...and we are done with the hard part.


Now if you were working with analytics.ear file, repack analytics.war and analytics.ear with the three updated files from above using JAR. If you were working with Custom Style Shared Folder, update / re-deploy the Weblogic Deployment for this.

After you are done with above, restart the entire OBIEE services including Weblogic.


Please Note: Depending on your OBIEE 12c version and patch level, the name of variables in above file maybe different. In such a case you would have to update the code from here with relevant variable names.


Now you could test this copy / paste functionality from your browser.

After OBIEE restart, open your browser and login into BI.

Open any page with a table / pivot on it. I have opened Product Pricing page from Sample App. My page looks different from typical OBIEE 12c pages because I am using a modified version of Skyros theme and not the default Alta theme.



Right Click on the Pivot and viola... You should now see the new items in the Context Menu.


If you are not able to see these options then ensure that the page / scripts are not loaded from browser cache and try again. Use Ctrl + F5 (in Chrome / Firefox) or Ctrl + R (in IE) to reload the page and scripts from server.


On selecting Copy Cell Value... option you would get:

A message box which will allow you to manually copy the cell value.



On selecting Copy Visible Table...(New Window) option you would get:

A new Window from which you should be able to copy the entire visible HTML Table as from main window.




So, in short (!!) you could now enable a more intrinsic copy/paste in OBIEE 12c. I hope this takes you one step more close to make life easier for yourself and for your Business.