Hi Vojtech,
Thanks again for the delivery of the patch. For revision 7, do you have
a list of content? I had previously indicated I could work on adding the
plugin API to launch a dialog, but hadn’t been able to get started on it
until now. I wanted to see if you by chance were already working on it
or if you were planning to deliver that yourself in the next revision?
A couple of other items we are looking for are the ability to add tasks
for execution and get access to the session ID or some kind of
authentication token so that we can make calls from our server into the
REST API. I’m not very familiar yet with the REST API so I’m not sure
what authentication methods are available and which would be best.
Oved - I remember we discussed UI plugins should be able to use same
logic as jasper reports for getting a session identifier for using the
REST API.
do you remember the details on this one?
Thanks,
Itamar
-Chris
*From:*engine-devel-bounces@ovirt.org
[mailto:engine-devel-bounces@ovirt.org] *On Behalf Of *Vojtech Szocs
*Sent:* Thursday, October 18, 2012 10:49 AM
*To:* engine-devel
*Subject:* [Engine-devel] UI Plugins: PoC patch revision 6 now available
Hi guys,
the latest revision of UI Plugins proof-of-concept patch is now
available for you to experiment with. You can download the patch from
oVirt Gerrit at
http://gerrit.ovirt.org/#/c/8120/2 (patch set 2).
Please read on to learn what's new in this revision. If you have any
comments, questions or ideas, please let me know!
------------------------------------------------------------------------
*0. UI plugin path information resolved using local Engine configuration**
*
Server-side UI plugin infrastructure now uses local (machine-specific)
Engine configuration instead of global (/vdc_options/ database table)
Engine configuration:
* Previously, path information was resolved through
org.ovirt.engine.core.common.config.Config class - Engine
configuration values were retrieved from /vdc_options/ database table.
* Currently, path information is resolved through
org.ovirt.engine.core.utils.LocalConfig class - Engine configuration
values are retrieved from local file system.
In case you're not working with oVirt Engine through RPM package system,
e.g. you have a local development environment set up and you build and
deploy oVirt Engine through Maven, please follow these steps:
a. Copy default Engine configuration into /usr/share/*ovirt-engine*/conf
# mkdir -p /usr/share/ovirt-engine/conf
# cp <OVIRT_HOME>/backend/manager/conf/engine.conf.defaults
/usr/share/ovirt-engine/conf/engine.conf.defaults
b. If necessary, copy UI plugin data files from
/usr/share/engine/ui-plugins to /usr/share/*ovirt-engine*/ui-plugins
c. If necessary, copy UI plugin config files from /etc/engine/ui-plugins
to /etc/*ovirt-engine*/ui-plugins
d, In case you want to override the default Engine configuration, put
your custom property file into /etc/sysconfig/ovirt-engine
The reason behind this change is that path information for UI plugin
data and configuration is typically machine-specific, and should be
customizable per machine through Engine local configuration.
------------------------------------------------------------------------
*1. New plugin API function: addMainTabActionButton
*
The "addMainTabActionButton" API adds custom context-sensitive button to
the given main tab's data grid, along with corresponding data grid
context menu item.
addMainTabActionButton(entityTypeName, label, actionButtonInterface)
/entityTypeName/ indicates which main tab's data grid the button should
be added to, according to the entity type associated with the main
tab./entityTypeName/ values are strings reflecting
org.ovirt.engine.ui.webadmin.plugin.entityEntityType enum members.
Following /entityTypeName/ values are currently supported (values are
case-sensitive): "DataCenter", "Cluster", "Host",
"Storage", "Disk",
"VirtualMachine", "Template".
Note: "Pool" value is currently not supported, because of
org.ovirt.engine.core.common.businessentities.vm_pools entity not
implementing the BusinessEntity interface, not sure why though. Maybe we
should switch from BusinessEntity to IVdcQueryable interface and always
cast getQueryableId method result value to Guid?
/label/ is the title displayed on the button/.
/
/actionButtonInterface/ represents an object that "implements the button
interface" by declaring its functions: /onClick/, /isEnabled/,
/isAccessible/. All functions of /actionButtonInterface/ receive
currently selected item(s) as function arguments.
Let's take a closer look at the concept behind /actionButtonInterface/.
In traditional class-based object-oriented languages, such as Java,
interface is an abstract type that contains method declarations without
an implementation. A class that implements the given interface must
implement all methods declared by that interface (unless it's an
abstract class, but this isn't relevant in our case).
In contrast with traditional class-based object-oriented languages,
JavaScript supports OOP through prototype-based programming model
(
https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Objec...).
At the same time, JavaScript language is dynamically-typed and therefore
doesn't support traditional concept of interface in OOP, it uses "duck
typing" technique instead (
http://en.wikipedia.org/wiki/Duck_typing).
The simplest way to provide an object that "implements the given
interface" in JavaScript is to use "duck typing" technique: providing an
object that contains well-known functions. In UI plugin infrastructure,
I call this concept "interface object", represented by
org.ovirt.engine.ui.webadmin.plugin.jsni.JsInterfaceObject class. Unlike
the traditional concept of interface abstract type in object-oriented
languages, an "interface object" _does not necessarily have to declare
all functions of the given interface_ in order to "implement" such
interface. In fact, an empty object can be used as a valid "interface
object". Missing functions will be simply treated as empty (no-op)
functions. Furthermore, an "interface object" can "implement"
multiple
interfaces by declaring functions of those interfaces (interface
composition).
Getting back to "addMainTabActionButton" API, here's a sample code that
adds new button to "Host" main tab data grid, as part of UiInit event
handler function:
UiInit: *function*() {
api.addMainTabActionButton('Host', 'Single-Host Action',
// Action button interface object
// All functions receive currently selected item(s) as function
arguments
{
// Called when the user clicks the button
onClick: *function*() {
// Calling 'arguments[0]' is safe, because onClick()
can be called
// only when exactly one item is currently selected in
the data grid
window.alert('Selected host entity ID = ' +
arguments[0].entityId);
},
// Returning 'true' means the button is enabled (clickable)
// Returning 'false' means the button is disabled
(non-clickable)
// Default value = 'true'
isEnabled: *function*() {
// Enable button only when exactly one item is selected
*return*arguments.length == 1;
},
// Returning 'true' means the button is visible
// Returning 'false' means the button is hidden
// Default value = 'true'
isAccessible: *function*() {
// Always show the button in the corresponding data grid
*return**true*;
}
}
);
}
As mentioned above, all functions of an interface object are optional.
For functions expecting return value, default value is defined by UI
plugin infrastructure. For example:
* onClick - no default value (no return value expected)
* isEnabled / isAccessible - default value "true" (boolean return
value expected)
Note: UI plugin infrastructure checks the actual return value type, and
uses default value in case the function returned something of wrong
(unexpected) type.
In the example above, "currently selected item(s)" maps to JSON-like
representations of business entities currently selected in the
corresponding data grid. For now, the entity representation is quite
simple and same for all entity types:
{ entityId: "[BusinessEntityGuidAsString]" }
In future, we will create specific JSON-like representations for
specific business entities, in compliance with Engine REST API entity
structure.
For a more extensive example of using "addMainTabActionButton" API,
please see the attached "addMainTabActionButton.html.example" file.
------------------------------------------------------------------------
*2. Improved plugin API function: addMainTab
*
The "addMainTab" API was improved to address following issues:
* "addMainTab" can now be called at any moment during UI plugin
runtime, given that the plugin is allowed invoke plugin API
functions (plugin is either INITIALIZING or IN_USE).
Previously, "addMainTab" worked reliably only when called from
within UiInit event handler function.
Currently, it's possible to call "addMainTab" at any moment, e.g.
from within some other event handler function (after UiInit has
completed).
* "addMainTab" now retains "active" tab (highlighted tab GUI).
"addMainTab" works by adding new tab component (GWTP presenter
proxy) and refreshing main tab panel GUI by removing all related
tabs and re-adding them again.
This logic is handled by
org.ovirt.engine.ui.common.presenter.DynamicTabContainerPresenter
class, which makes sure that "active" tab is retained even after
main tab panel was refreshed.
Furthermore, custom main tab implementation now displays the content of
the given URL through HTML iframe element.
------------------------------------------------------------------------
*3. Improved native JavaScript function handling* (GWT JSNI)
This patch introduces
org.ovirt.engine.ui.webadmin.plugin.jsni.JsFunction and
org.ovirt.engine.ui.webadmin.plugin.jsni.JsFunctionResultHelper classes
providing Java abstraction for invoking native JavaScript functions.
These classes follow the general contract of "interface object" as
mentioned above.
JsFunctionResultHelper is particularly useful when dealing with
functions which are expected to return value of a certain type. Too bad
standard GWT JSNI classes don't provide such abstraction for working
with native functions out-of-the-box...
------------------------------------------------------------------------
*4. ActionPanel and ActionTable type hierarchy refactoring* (related to
"addMainTabActionButton" API)*
*
Previously, AbstractActionPanel and AbstractActionTable classes didn't
implement any reasonable interface that would allow other components
(client-side UI plugin infrastructure) to depend on their functionality
in a loosely-coupled manner. This would make code that implements
"addMainTabActionButton" API "ugly": main tab view interface would
have
to reference AbstractActionTable class directly. In MVP design pattern,
view interface should avoid referencing specific GWT Widget classes
directly.
This patch introduces new interfaces for ActionPanel and ActionTable
components while eliminating code redundancy (duplicate or unnecessary
code).
------------------------------------------------------------------------
*5. ActionPanel type hierarchy refactoring* (related to "addMainTab" API)
Since org.ovirt.engine.ui.common.presenter.DynamicTabContainerPresenter
defines new DynamicTabPanel interface that extends standard GWTP
TabPanel interface, some refactoring had to be done in related
ActionPanel classes.
This patch makes sure that both
org.ovirt.engine.ui.common.widget.tab.AbstractTabPanel (widget) and
org.ovirt.engine.ui.common.view.AbstractTabPanelView (view) support
DynamicTabPanel interface.
Note that for now, only main tab panel
(org.ovirt.engine.ui.webadmin.section.main.presenter.MainTabPanelPresenter)
supports dynamic tabs within its view.
------------------------------------------------------------------------
*Where is addSubTab API function?*
Implementing "addSubTab" API requires some more changes, and I didn't
want to delay this PoC patch just because of it...
Here's a sample code that illustrates proposed "addSubTab" API usage:
UiInit: *function*() {
api.addSubTab('Host', // entityTypeName
'Custom Host Sub Tab', // label
'custom-host-sub-tab', // historyToken
'http://www.ovirt.org/', // contentUrl
// Sub tab interface object
// All functions receive currently selected item(s)
// within the main tab data grid as function arguments
{
// Returning 'true' means the sub tab is visible
// Returning 'false' means the sub tab is hidden
// Default value = 'true'
isAccessible: *function*() {
*return*arguments.length == 1 && arguments[0].entityId ==
'<MyHostEntityId>';
}
}
);
}
As part of "addSubTab" API implementation, I'll refactor custom main tab
components, in order to use one "tab type" for both main and sub tabs.
Currently, we have one (and only one) "tab type" - a tab that shows
content of the given URL through HTML iframe element.
We could also create new "tab types", e.g. form-based tab that shows
key/value pairs (IMHO this could be quite useful for custom sub tabs).
------------------------------------------------------------------------
Let me know what you think!
Cheers,
Vojtech
_______________________________________________
Engine-devel mailing list
Engine-devel(a)ovirt.org
http://lists.ovirt.org/mailman/listinfo/engine-devel