Friday, October 21, 2011

PUC Perspectives

Pentaho User Console Perspective Documentation for Developers


What is a perspective?

Perspectives are a specialized mode added to the Pentaho User Console (PUC) for the next release of the suite (SUGAR). Active development is still very much underway, but I wanted to highlight this really cool new feature.  CI builds of Sugar are available at http://ci.pentaho.com/view/Sugar/.

A perspective changes the behavior and appearance of PUC by taking over certain areas of the interface. The PUC main toolbar and main menubar are now easily setup with XUL overlays. The content area of PUC can also be completely owned by a perspective.  In this way, PUC can be dramatically customized.  Switching perspectives is done by clicking on them in the upper right hand corner.



How to register a perspective

From an API standpoint, registering a perspective with the system simply means adding the right objects to the perspective manager. There are two interfaces of concern here, IPluginPerspective and IPluginPerspectiveManager.  IPluginPerspectiveManager is added to pentahoObjects.spring.xml making it available through PentahoSystem. The easiest way to add a perspective to the system is to simply add its definition to the plugin.xml of a plugin.  However, you are not constrained to this, you can register a new perspective through the API. For example,

IPluginPerspective perspective = new DefaultPluginPerspective();
perspective.setId(..);
perspective.setOverlays(..);
etc.

IPluginPerspectiveManager manager = PentahoSystem.get(IPluginPerspectiveManager.class, getPentahoSession()); 
manager.addPluginPerspective(perspective);


Implementing the interfaces

Should you decide to implement your own perspective interfaces and replace ours, there are only a few interfaces to concern yourself with. The first thing you must do is replace the IPluginPerspectiveManager in pentahoObjects.spring.xml, for example:

<bean id="IPluginPerspectiveManager" class="com.yourcompany.BetterPerspectiveManager" scope="singleton" />

Once you've done this, and your class is available to the system your plugin perspective manager will be used to register perspectives. PUC will use PentahoSystem to use your manager to list the available perspectives. A perspective itself must extend IPluginPerspective, for example:

public class MyPluginPerspective extends IPluginPerspective {
 ..
}

This class is just a bean and provides:
id (unique perspective id)
title (name of the perspective shown in PUC)
content-url (the url of the page used to hijack PUC content area)
resourcebundle (the uri to a message bundle for localizing the title of the perspective)
overlays (xul overlays to apply to menu/toolbar of PUC)
layout-priority (used to control the order which perspectives show up in PUC, BI Browser is -1)
required-security-actions (action based security can be used to check if the user "isAllowed")



The easy way: How to define perspectives in plugin.xml

The plugin system in Pentaho has been expanded to read perspective definitions from the plugin.xml of a plugin in pentaho-solutions. Any number of perspectives can be added to a single plugin.xml.

<plugin title="My Plugin" name="my-plugin">
  <perspective id="myperspective1" title="Perspective 1" layout-priority="1">
  </perspective>
  <perspective id="myperspective2" title="Perspective 2" layout-priority="2">
  </perspective>
...and so on
</plugin>

If you want to localize the title of the perspective as it appears in PUC you'll need a resource bundle accessible to PUC at runtime. The URI is specified on the perspective definition:

<perspective id="myperspective1" title="${title}" layout-priority="1" resourcebundle="content/default-plugin-perspective/resources/messages/messages">
</perspective>

The plugin "default-plugin-perspective" is a folder in pentaho-solutions/system and contains a messages.properties file located in 'default-plugin-perspective/resources/messages'. This can be made available by publishing a static-path in the plugin config:

<static-paths>
 <static-path url="/default-plugin-perspective/resources" localFolder="resources"/>
</static-paths>

The string ${title} is replaced with whatever title means in the messages.properties file (or other localized
bundles).

As mentioned before, a perspective will takeover the content area of PUC when it is active. The URL for this is specified with the content-url attribute of the perspective. For example:

<perspective .... content-url="content/default-plugin-perspective/resources/html/index.html">

When this perspective is made active, index.html is loaded in an iframe in the content area of PUC.

Action based security may be used to lock perspectives down, for example, only show an "Admin" perspective to those who are allowed to see it. This is done using the existing action based security provided by the Pentaho platform.  The security action for administration is "org.pentaho.security.administerSecurity". To specify this in the perspective definition:

<perspective .... required-security-action="org.pentaho.security.administerSecurity">

In PUC, the order that the perspectives show up in the UI is controlled by a layout-priority attribute in the perspective node. The default perspective (BI Browser) has a value of -1. If you want your perspective to appear before this go with a lower number (such as -2).

One of the most fundamental changes to PUC for the addition of perspectives was to completely replace the menu system with a XUL approach. We already had a XUL definition for the main toolbar, now there is a XUL definition for the main menubar. This file is webapps/pentaho/mantle/xul/main_menubar.xul. You can further customize PUC by changing this XUL file. What we are interested for the purpose of perspectives is the ability to modify the toolbar and menubar with XUL overlays. A perspective can provide any number of XUL overlays which are used to add/remove/update items to the toolbar or menubar. Here is a simple example:

<perspective>
 <overlay id="myoverlay"  resourcebundle="content/default-plugin-perspective/resources/messages/messages">
        <toolbar id="mainToolbar">
            <toolbarbutton id="mybuttonid" image="../content/default-plugin-perspective/resources/images/enabled32.png" onclick="mainToolbarHandler.executeMantleFunc('defaultTestPerspectiveFunction();')" tooltiptext="${mybutton}"  insertafter="dummyPluginContentButton"/>
        </toolbar>   
 </overlay>
</perspective>

This XUL overlay will add a button to the main toolbar with a given image, title, tooltip, insertion point, etc.  Menu items can be added anywhere in the menu system in a similar way:

<perspective>
 <overlay id="myoverlay"  resourcebundle="content/default-plugin-perspective/resources/messages/messages">
  <menubar id="mainMenubar">
   <menubar id="newmenu">
    <menuitem id="mymenuitemid" label="${mymenuitemlabel}" command="mainMenubarHandler.executeMantleFunc('defaultTestPerspectiveFunction();')" />  
   </menubar> 
  </menubar> 
 </overlay>
</perspective>

This will add a menuitem under the File -> New menu. You can actually add an entirely new menu if needed.  This can be done by giving a more complete definition in the XUL:

<menubar id="mainMenubar">
 <menubar id="mymenu" label="${mymenu}" layout="vertical" insertafter="toolsmenu">
  <menuitem id="mymenuitem" label="mymenuitem" js-command="alert('Item Clicked')" />  
 </menubar>
</menubar>

When a perspective is active, its overlays will be in play, likewise, when it becomes inactive, its overlays are removed. It is possible for a perspective's overlays to make permanent changes to the UI, instead of just when it is active. This is done simply by convention, an overlay whose "id" starts with "sticky" or "startup" will be active even when the perspective is not. Typically, you would create two overlays for a perspective, one for changes to apply when the perspective is active and another to apply because the plugin exists.

For example:


<overlay id="sticky.myoverlay" resourcebundle="content/default-plugin-perspective/resources/messages/messages">
...

This overlay will be applied regardless of the active state of the perspective.

Interactivity

It is now possible to more easily interact with the main menubar and toolbar with JavaScript. If your content-url points to an HTML page you can use JavaScript to enable/disable any menu item or toolbar button

To read the state (enabled/disabled) of a toolbar button:
var enabled = window.top.mantle_isToolbarButtonEnabled("button.id");

To set the state of a toolbar button:
var enabled = true; // or false
window.top.mantle_setToolbarButtonEnabled("button.id", enabled);

To read the state of a menu item:
var enabled = window.top.mantle_isMenuItemEnabled('menuitem.id'))

To set the state of a menu item:
window.top.mantle_setMenuItemEnabled('menuitem.id', enabled)

We have also made it easier to bridge between the XUL/GWT world for defining what happens when you press a button or select a menu item. Of course, there are the existing techniques, referencing the handler and a bound function to invoke a well known PUC command, for example:

<toolbarbutton .. onclick="mainToolbarHandler.executeMantleCommand('SaveCommand')" />
<menuitem .. command="mainMenubarHandler.executeMantleCommand('OpenFileCommand')" />  

If you want to invoke arbitrary JavaScript with a toolbarbutton or menuitem, you can alternatively define a
js-command attribute in the XUL definition. For example:

<menuitem .. js-command="alert('Hello Perspective')" />  

When the menu item is pressed the JavaScript alert function will pop a message saying "Hello Perspective". In this way, we can invoke JavaScript functions defined by the page living at the content-url. Since we are crossing the boundaries of iframes and we don't necessarily know where to call the function, we always eval the JavaScript as it is provided to us. What this means is that you should define any functions you want visible to the toolbar buttons or menu items at the "top" window. An example of this:

<script type="text/javascript">
 window.top.myPerspectiveFunction = function() {
  alert('Success!');
 }
</script>

To invoke this function from a toolbar button or menu item:
<menuitem .. js-command="myPerspectiveFunction()" />
  

Update: 11/1/2011
We have added activation/deactivation hooks, if the document (content-url) contains javascript functions "perspectiveActivated" and "perspectiveDeactivated" then we the perspective system will invoke them at the appropriate time. This can be used for any purpose you see fit, practically however, this was created to allow the persistence of perspective state that might otherwise be lost. For example, when a perspective is activated (or reactivated) we can restore the toolbar state (buttons enbabled/disabled). Without the behavior, the XUL overlays are reapplied from the beginning without knowledge of any application state. An example:

<script type="text/javascript">
 
  var testButtonEnabled = true; 
  
  perspectiveActivated = function() {
    window.top.mantle_setToolbarButtonEnabled('some.button.id', testButtonEnabled);
  }  
  
  perspectiveDeactivated = function() {
    testButtonEnabled = window.top.mantle_isToolbarButtonEnabled('some.button.id');
  }  
  
</script>



This example has been checked in along with the existing sample plugin perspective.


Additional JavaScript interaction has been added as well.  You can get a list of perspectives (array of perspective ids) and activate a perspective by id.  Example use:

window.top.mantle_getPerspectives()
and
window.top.mantle_setPerspective(id)


Sample Perspective

Many of the features and functionality discussed here are available as a self-documenting sample plugin perspective located in pentaho-solutions/system/default-plugin-perspective/plugin.xml.

Thursday, October 13, 2011

Platform Update - Consolidation & Cleanup

I wanted to take a few minutes to detail the progress in the platform projects. As most of you know, we have gone through a round of project consolidation in the platform. The new projects are as follows:

===================================================
api - unchanged

core

- bi-platform-engine-core
- bi-platform-engine-security
- bi-platform-engine-services
- bi-platform-test-foundation
- bi-platform-ui-foundation
- bi-platform-util
- bi-platform-security-userroledao

The engine-* projects had previously enjoyed a GPL license, this remains unchanged, however, the license the projects brought into the core is now GPL if not already.

repository - relatively unchanged (this is the JCR)

scheduler - we have dropped the old scheduler, scheduler2 takes over

extensions
- bi-platform-web
- bi-platform-web-servlet
- bi-platform-plugin-actions
- bi-platform-plugin-services

user-console - though future plans for mantle include making it a plugin, it is out of the old bi-platform-v2/trunk and renamed

assembly
- bi-platform-appserver
- bi-platform-assembly
- bi-platform-build
- bi-platform-sample-data
- bi-platform-sample-solution

We have also removed several projects, such as portlet, legacy, and test-solution.

The dependency order is also, as I have listed the projects:
api, core, repository, scheduler, extensions, user-console
===================================================

All of the new projects produce new ivy artifacts, as to not overwrite anything already in artifactory. We have incorporated changes to subfloor allowing the publishing of test jars. For example, the core project's test-src contains classes essential for testing in many downstream projects.

api - pentaho-platform-api.jar
core:
- pentaho-platform-core.jar
- pentaho-platform-core-test.jar (BaseTest and friends)
repository:
- pentaho-platform-repository.jar
- pentaho-platform-repository-test.jar
scheduler - pentaho-platform-scheduler.jar
extensions - pentaho-platform-extensions.jar
user-console - pentaho-user-console.jar (typically resolved is the package/zip)

With the introduction of the JCR the notion of "solution" "path" "name" goes away and we simply have a single path to the resource "/path/to/the/resource". I have modified the API of SolutionEngine so that it only accepts this single path element and fixed all (known) downstream implications.

Unit tests are in outstanding shape right now and cobertura coverage is being published. I spent 2 days doing nothing but getting unit tests working again. Many of the unit tests which are now working have not been working since 2009 (some even earlier than that). Plenty of unit tests were commented out, where possible, I have uncommented these and they are now working. The unit test summary:

core - 100% unit test pass
repository - 100% unit test pass
scheduler - 100% unit test pass
extensions - out of 406 unit tests, 5 are failing, these failures are the real deal, they warrant investigation (they might have caught something)

So for the entire BI platform, there are only 5 unit tests which are failing! We're going to watch these projects carefully and keep the unit tests passing (and squash any failures). Over time we'll be adding new tests, and increasing the code coverage.

The assembly has been dramatically cleaned up and simplified. The assembly project does not require the checkout of any other projects, it has all the resources required to lay down the solutions, data, appserver, etc as well as ivy dependencies on package archives of other projects. You can simply checkout the assembly project, run an ant build and a BI-SERVER build will be created.

CI is running jobs for each of the new projects with the new build order in place. These can be seen in the SUGAR grouping:
http://ci.pentaho.com/view/Sugar/

Lastly, I have officially "closed" bi-platform-v2/trunk the new location in SVN for these projects is:
svn://source.pentaho.org/svnroot/pentaho-platform/trunk
The bi-platform-v2/trunk has been renamed as trunk-closed to further discourage any checkins to the wrong location.

Any SUGAR development and 4.1/4.5 fixes should be incorporated in the new project structure (though the project names have remained the same, using the summary I listed above you can easily find where to checkin).