For each pivot table cell where the plug-in is used, the plug-in performs either a DRILLTHROUGH or DRILLFACTS query (depending on the value of LISTINGSOURCE) and returns the fields specified by LISTINGFIELDS or %OnGetListingFields() (as applicable). It then passes the field values to the %OnCompute() method. This method has the following signature:
Method %OnCompute(pSQLRS As %SQL.StatementResult, pFactCount As %Integer) As %Status
Where:
-
pSQLRS is an instance of %SQL.StatementResultOpens in a new tab that contains the fields specified by LISTINGFIELDS or %OnGetListingFields().
For information on using this class, see Using Dynamic SQL in Using InterSystems SQL.
-
pFactCount is total number of facts in the given context.
In your implementation of this method, do the following:
-
Iterate through the statement result. To do so, use the %Next() method of this instance.
-
As needed, retrieve values for each row. The statement result instance (pSQLRS) provides one property for each field in the listing query; the name of the property is the same as the field name.
For example, in the previous section, %OnGetListingFields() retrieves a single field, MxTextScore. In this case, pSQLRS has a property named MxTextScore.
-
Perform the desired computations.
-
Set the properties of the plug-in instance, as described in Defining Advanced KPIs. At a minimum, set the following properties:
-
%seriesCount — Specifies the number of series (rows) in this plug-in.
InterSystems recommends that plug-ins have only one series. (For plug-ins with PLUGINTYPE equal to "Pivot", when a user drags and drops a plug-in property, the Analyzer uses only the first series.)
-
%seriesNames(n) — Specifies the name of the series n, where n is an integer.
-
%data(n,propname) — Specifies the value of the given property (propname), for the series n.
The property name must exactly match the name of a <property> in the XData block.
For example:
// place answer in KPI output
set ..%seriesCount = 1
set ..%seriesNames(1) = "PluginDemo"
//set Count property of KPI -- just use received pFactCount
set ..%data(1,"PatientCount") = pFactCount
// iterate through result set to get HighScoreCount
set n = 0
set highcount = 0
while (pSQLRS.%Next()) {
set n = n + 1
set testscore = pSQLRS.MxTestScore
if (testscore>95) {
Set highcount = highcount + 1
}
if (pSQLRS.%SQLCODE < 0) {write "%Next failed:", !, "SQLCODE ", pSQLRS.%SQLCODE, ": ", pSQLRS.%Message quit}
}
set ..%data(1,"HighScoreCount") = highcount
This is an extract from the sample class BI.Model.KPIs.PluginDemo, which is available in the Analyzer for use with the Patients cube.
Indicating State of Completion
Plug-ins are executed asynchronously. When a query containing plug-ins is executed, the query can be complete before the plug-ins have completed execution. In this case, there are cells whose results are pending. Within these cells, you can display the plug-in current status (as the string n% complete). To do so, within %OnCompute(), periodically invoke the %SetPercentComplete() instance method; the argument is an integer between 0 and 100. For example, you could do the following while iterating through the statement result:
// update pct complete
If (n#100 = 0) {
Do ..%SetPercentComplete(100*(n/pFactCount))
}
The appropriate approach depends on the logic in %OnCompute(). In some cases, the majority of the computation time might occur outside of this iteration.
The pivot table automatically refreshes when the results are available.
Creating a Plug-in for Multiple Cubes
The previous sections describe how to create a plug-in that can be used with a single cube or subject area. You can also create a plug-in that can be used in multiple cubes. In practice, this is difficult to do because it is usually necessary to programmatically determine the fields to query.
To create a plug-in that you can use with multiple cubes, use the following additional instructions:
-
Specify the BASECUBE class parameter as one of the following:
This option determines which cubes and subject areas can use the plug-in.
-
Include the following filter definition within the XData block:
<filter name="%cube" displayName="Subject Area" />
The name must be %cube but you can use any value for the display name.
When you use this plug-in within the Analyzer (if applicable), the system passes the name of the current cube or subject area to this filter. Similarly, when you use this plug-in within an MDX query, the FROM clause of the query determines the value of this filter.
-
Implement the %OnGetMDX() method so that it uses the value of the %cube filter. For example:
Method %OnGetMDX(ByRef pMDX As %String) As %Status
{
Set tBaseCube = ""
// Use %cube filter to find the base cube
If $IsObject(..%filterValues) {
If (..%filterValues.%cube'="") {
Set tBaseCube = ..%filterValues.%cube
}
}
If (tBaseCube'="") {
Set pMDX = "SELECT FROM "_tBaseCube
}
Quit $$$OK
}
-
Ensure that the listing query can work with all the desired cubes and subject areas. Either:
-
For hardcoded listings, use only fields that are suitable in all cases.
-
Programmatically determine the fields to use.
For examples, see %DeepSee.PlugIn.MedianOpens in a new tab and %DeepSee.PlugIn.PercentileOpens in a new tab.
Determining the Listing Fields Programmatically
If the query for the plug-in specifies LISTINGSOURCE as "FactTable", there are additional tools that enable you to programmatically determine the fields to use in %OnGetListingSQL(). You can do the following:
-
Include the following filter definition within the XData block:
<filter name="%measure" displayName="Measure" />
The name must be %measure but you can use any value for the display name. This filter provides a list of all measures defined in the applicable cube or subject area.
-
Implement the %OnGetListingSQL() method as follows:
-
Examine the value of the %measure filter.
-
Use the %GetDimensionInfo() method of the %DeepSee.Utils class to retrieve, by reference, information about the selected measure.
Use this information as input for the next step.
-
Use the %GetDimensionFact() method of the %DeepSee.Utils class to retrieve the name of the field that stores the selected measure.
-
Optionally implement the %OnGetListingOrderBy() and %OnGetListingMaxRows() callbacks. For details, see the class reference for %DeepSee.KPIPlugInOpens in a new tab.
For examples, see %DeepSee.PlugIn.MedianOpens in a new tab and %DeepSee.PlugIn.PercentileOpens in a new tab. Also see the class reference for the %DeepSee.Utils class.
Filtering the Listing
Plug-ins provide a feature that is not available in other scenarios: namely, the ability to specify which records to use when a detail listing is displayed. By default, when a user requests a detail listing for a given cell or set of cells in the results, the system displays a listing that shows all the records associated with those cells. In some cases, however, it is preferable to show a subset of them. For example, the sample class BI.Model.KPIs.PluginDemo has a plug-in property called HighScoreCount. The following shows an example MDX query that uses this plug-in property as a measure:
SELECT NON EMPTY {[Measures].[%COUNT],%KPI("PluginDemo","HighScoreCount",,"%CONTEXT")} ON 0,
NON EMPTY [AllerSevD].[H1].[Allergy Severities].Members ON 1 FROM [PATIENTS]
Patient Count HighScoreCount
1 Nil known allergi 158 12
2 Minor 113 7
3 Moderate 103 5
4 Life-threatening 133 9
5 Inactive 122 8
6 Unable to determi 119 6
7 No Data Available 385 29
Consider the row for Nil known allergies. If you display a listing for either cell, by default, the system displays a listing that consists of 158 records, because there are 158 patients with no known allergies. But the purpose of the HighScoreCount measure is to count the patients with scores above a given threshold, so when we display the detail listing for the cell HighScoreCount in this row, we might prefer to see only the patients with scores above that threshold.
To apply this sort of filtering to a plug-in, include the following logic in your implementation of %OnCompute(), for any source class ID that should be shown in the listing:
set ..%data("IDLIST",pluginProperty,sourceClassID) = ""
Where pluginProperty is the name of the plug-in property that should use this filtering, and sourceClassID is the ID in the source class. (The ID should be a source class ID even if plug-in otherwise uses the fact class. To make the source class ID available to the plug-in, add %sourceId to the field list.)
For a given plug-in property, if %data("IDLIST",pluginProperty) is not defined, the listing shows all the records associated with the given cell or cells.
Example
To see an example, edit the sample class BI.Model.KPIs.PluginDemo as follows:
-
Change LISTINGFIELDS to be the following:
Parameter LISTINGFIELDS As STRING = "%sourceId,MxTestScore";
-
Find the part of %OnCompute() that sets the highcount variable, and modify it as follows:
if (testscore>95) {
Set highcount = highcount + 1
Set tHighScoreId = pSQLRS.sourceId
Set ..%data("IDLIST","HighScoreCount",tHighScoreId)=""
}
-
Save and recompile the class.
Then, in the Analyzer, create a pivot table that uses both properties of this plug-in (for purposes of comparison). Select a cell that displays the HighScoreCount property, display a listing, and notice that only patients with a high score are shown. For contrast, select a cell that displays the PatientCount property and display a listing for that. In this case, you will see patients with all scores.
Available Error Logging
If a plug-in encounters an error, the system writes to the error log file in the manager’s directory. The name of this file is DeepSeeTasks_namespace.log.
Defining a Calculated Member That Uses a Plug-in
For any plug-in (and any other KPI), you can create a calculated member that retrieves values from it. Then users can drag and drop this member within the Analyzer. To create such a calculated member:
-
Define a calculated measure as described in Defining a Calculated Measure, in Defining Models for InterSystems Business Intelligence.
-
For Expression, specify an MDX expression of the following form:
%KPI(pluginname,propertyname,seriesname,"%CONTEXT")
Where pluginname is the name of the plug-in, propertyname is the name of the property, and seriesname is the name of the series. You can omit seriesname; if you do, this function accesses the first series in the plug-in.
"%CONTEXT" is a special parameter that provides row, column, and filter context to the plug-in; this information is passed to the base MDX query used by the plug-in.
For example (for a plug-in with only 1 series):
%KPI("PluginDemo2","Count",,"%CONTEXT")
For plug-ins with PLUGINTYPE equal to "Pivot", when a user drags and drops a plug-in property, the Analyzer automatically uses syntax like this in the underlying MDX query that it generates.
For additional options, see the %KPI function in the InterSystems MDX Reference.