Report: How to modify and create custom report

Introduction

Report, the dark side of Sailpoint. At first sight, the reports on Sailpoint may seem difficult to create and manage and it is true, but they have their own logic that once understood will make everything more simple.

On this blog, I want to explain the structure, the main components of a report and how they interlace with each other. Finally, I want to share with the community a report template with a special configuration and with the possibility of adapting to all cases.

Report are a type of taskDefinition object and to build a report we two subtype of this object.
The first is the Template, where is stored the logic of report: what is the data and what do I do with it.
The second is the LiveReport, where the inputs are defined and how we want to see them. A LiveReport is an instance of a Template.

From a Template, we can generate multiple LiveReport with different characteristics:

Template Structure

A template is composed of some part, where we can define on what data we want work, the inputs and we can rework the data during the generation of report.

The main part of a report are Signature, RequiredRights and Attributes.

In Signature we can define all input arguments, for example the values come from a Form.
In RequiredRights we can associate the SPRights, to allow its visibility or not.
In Attributes, the most important section, we define the source, the logic, the columns and if is present an input Form.

DataSource & Parameters

There are 3 type of DataSource: Filter, Java and HQL.

Filter

The most intuitive to use and recommended for beginners. We can say that it’s like writing a query in XML format.
In this type, we define an objectType of Sailpoint like a principal data-subset and later we can filter or add other object (as long as they can be united). Example:

With this, we are taking all the identity class.

<DataSource defaultSort="application.name,value,identity.name" objectType="Identity" type="Filter">

Later we can add a filter or join other object with the paramaters.

<Join joinProperty="IdentityEntitlement.identity" property="name"/>
<QueryParameters>
    <Parameter argument="application" property="application.id"/>
<QueryParameters>

This configuration returns all the Identity values with the name of correlated IdentityEntitlement filter for the application/s received in input.

In this typology it is possible to use other type of filter like Query\QueryScript where you can write\compose a HQL query or OptionsRule\OptionsScript to assing a rule\script where you compose a Filter.

Java

Maybe the most complex to use or to modify a report. With typology we can assing a Java class like source and use all its methods. Furthermore, all the attributes in the report and the signatures are passed to the class.

 <DataSource dataSourceClass="sailpoint.reporting.datasource.AccountAttributesLiveReportDataSource" objectType="Identity" type="Java">

For example, this source will work on the identities through AccountAttributesLiveReportDataSource class.

This typology can help us to provide reports quickly if the class already exist and have all the funcionality that we need or we can build ours class.
On the other side, build a class requires time and if you want modify an existing report, is essential to know tthe class used.

HQL

With this class of DataSource, we can write directly query based on HQL language. For all those who have experience in sql it will be very easy to understand and learn.
Similar to Filter type, we can write a query and implement Parameters and QueryScript or we can insert the join and where into the query.

An example:

<Query> from Link l, Identity identity 
               left outer join identity.manager m
               where l.identity.id = identity.id and l.application.id in(:application)</Query>
  <QueryParameters>
    <Parameter argument="application" property="application"/>
  </QueryParameters>

This query return all the accoutn with relative identity and manager, filter by application.
Also, we can remove and l.application.id in(:application) from query and implent a control on input paramater with a QueryScript:

<QueryScript>
    <Source>
        Object applications = args.get("application");
        if (applications != null && !applications.isEmpty()){
          query+= "and l.application.id in(:application)";
        }
        return query;
    </Source>
</QueryScript>

Columns

Obviously, this section we declare all the columns that we want into the report and we can do static or dynamic. I am referring to the static column for the object ReportColumnConfig and to dynamic for the object ExtendedColumnt.
ReportColumnConfig is static because every columns are declared like this, will always be present into the report even if it can be hidden.
ExtendedColumn is dynamic because we can write a script that can create columns based on parameters and the result can change everytime.

ReportColumnConfig

With a ReportColumnConfig object we define a static column, where we assign a property. A property is an attribute of any one object we are included in the DataSource, the lastname of Identity or the value of a ManagedAttribute.

<ReportColumnConfig field="Lastname" header="Lastname" property="identity.lastname" sortable="true" width="110"/>

We can work on the value of property, adding a rule or a script to printing an other value that depends from the property.

ExtendedColumnScript & ExtendedColumnRule

This object allows us to build a dynamic columns schema, starting from the parameters that come from a Form for example. We can create columns based on the applications or on type of identities.

We can build columns with all attributes that we need, render script\rule too. In every case, the rutern of script\rule must be a list of ReportColumnConfig objects and they will be automatically added to the Columns.

About this I want to talk better in the next section where I will share an interesting template.

My Custom Report

Custom Report.xml (9.7 KB)

This is a interesting template with this configuration:

  • DataSource: HQL
  • Object in DataSource: Link, Identity, Manager, ManagedAttribute, IdentityEntitlement and Application
  • Section: Form, ExtendedColumnScript, QueryParameters and QueryScript
  • Input: List of Application amd optional query critiria

I will explain the most important part so that you can understand its flexibility and adapt it to your purposes.

Form & Inputs

From the Form return a list of application and a string where the user can add an additional HQL filter. Both can be null.

<Argument name="queryCriteria" type="string"/>
<Argument multi="true" name="application" type="Application">
  <Description>rept_app_account_grp_memb_input_application_desc</Description>
  <Prompt>rept_app_account_grp_memb_input_application</Prompt>
</Argument>

QueryParameters and QueryScript

Like parameter are present the argument application(comes from Form) and two default value for reading only aggregated ManagedAttribute and connected IdentityEntitlement.

<QueryParameters>
     <Parameter argument="application" property="application"/>
     <Parameter defaultValue="Connected" property="ie.aggregationState" valueClass="sailpoint.object.IdentityEntitlement$AggregationState"/>
     <Parameter defaultValue="true" property="ma.aggregated" valueClass="boolean"/>
</QueryParameters>

In the script I check the the arguments values and them to HQL query if they are not empty.

<QueryScript>
  <Source>
        String scriptCriteria ="";
        Object applications = args.get("application");
        if (applications != null && !applications.isEmpty()){
          scriptCriteria =  " l.application.id  in( :application ) ";
        }

        String addCriteria = args.get("queryCriteria");
        if (addCriteria!= null  && !addCriteria.isEmpty()){
          if (!scriptCriteria.isEmpty())
            scriptCriteria+=" and ";
          scriptCriteria +=  addCriteria ;
        }

        if (!scriptCriteria.isEmpty())
          query+= " and " + scriptCriteria;
        return query;
    </Source>
</QueryScript>

ExtendedColumnScript

About me this is the best part. The script read the list of application and serch on the application a custom object called reportColumnMap. This object is a map that contains a list of application attributes that I want to import into this report.
We also provided that you can associate a rule to be inserted as renderRule or configure the column property with the Link or the Identity.

<ExtendedColumnScript>
  <Source> 
      import java.util.List;
      import java.util.Map;
      import sailpoint.reporting.*;
      import sailpoint.object.ReportColumnConfig;
      import sailpoint.object.Application;
      import sailpoint.object.Rule;

      List newCols = new ArrayList();
      Map formValues = form.getFieldValues();

      if (formValues != null && formValues.containsKey("application") && formValues.get("application") != null){
        String appid = formValues.get("application").get(0);
        Application application = context.getObjectById(Application.class,appid);
        Map simpleReportMap = application.getAttributeValue("reportColumnMap");

        if (simpleReportMap == null)
          throw new Exception("No column map defined for application " + application.getName());

        int i=0;
        for (String k : simpleReportMap.keySet()) {
          String fieldConf = simpleReportMap.get(k);
          if (fieldConf != null) {
            String field=null;
            String renderRuleName=null;
            ReportColumnConfig config = null;
            if (fieldConf.contains("#")) {
              field= fieldConf.split("#")[0];
              renderRuleName=fieldConf.split("#")[1];
            }
            else{
              field=fieldConf;
            ​}
            if (field.equals("_IDENTITY_")) {
              config = new ReportColumnConfig("identity"+ (i++) , k , "java.lang.String");
              config.setProperty("identity.id" );
            } else if (field.equals("_LINK_")) {
              config = new ReportColumnConfig("link"+ (i++) , k,"java.lang.String" );
              config.setProperty("l.id" );
            }else {
              config = new ReportColumnConfig("linkFieldSuffix"+ (i++) + field, k, "java.lang.String");
              config.setProperty(String.format("l.attributes.%s", field));
            }
            if (renderRuleName != null) {
                Rule renderRule = context.getObjectByName(Rule.class, renderRuleName);
                if (renderRule != null) {
                config.setRenderRule(renderRule);
              }
            }
            newCols.add(config);
          }​
        }
        ReportColumnConfig config = new ReportColumnConfig("userSource", "User Source", "java.lang.String");
        config.setProperty( "l.application.name" );
        newCols.add(config);
      }
      return newCols;
  </Source>
</ExtendedColumnScript>

This is a example of reportColumnMap:

<reportColumnMap>
    <String>Name</String>
    <String>_IDENTITY_</String>
    <String>_LINK_#RuleLink</String>
    <String>StartDate#RuleDate</String>
</reportColumnMap>

For the first the column will have l.attributes.name like property.
The second will have identity.id like property(the id of identity) and we can managed with a script for example.
The third will have β€œl.id” like property (the id of link) and RuleLink as RenderRule.
The fourth will have l.attributes.StartDate like property and RuleDate as RenderRule.

Conclusion

I hope this helps you create or modify reports that at first glance can be complicated and demoralizing. As always, the limit is your imagination.

4 Likes