Chapter 13. Send To

13. Send To

In this chapter we explain how to add custom "send to targets" to the Send to... menu within the report executor. These can be used to, for example, upload a report to a web service.

Note that, as of ReportServer 4.3.0, script datasinks are supported and are the recommended way to send reports to a given custom target. Scheduling is of course also supported. Details can be found in Chapter 7. Nevertheless, in case you need custom forms, you can use the SendTo functionality as explained below.

To add a custom target we need to implement net.datenwerke.rs.core.service.sendto.hooks.SendToTargetProviderHook which consists of three methods: consumes, getId, and sendTo. The getId method is used to define a unique identifier to identify the send to target. The consumes method takes as input a report object and allows to specify a target configuration. Finally, the sendTo method is called to execute the action. The basic outline to define a target is thus the following code snippet

import net.datenwerke.rs.core.service.sendto.hooks.SendToTargetProviderHook
import net.datenwerke.rs.core.service.sendto.hooks.adapter.SendToTargetProviderHookAdapter
import net.datenwerke.rs.core.client.sendto.SendToClientConfig

def HOOK_NAME = 'MY_SEND_TO_EMAIL'

def callback = [
  consumes : { report -> 
    def config = new SendToClientConfig()
    config.title = 'Custom Send To'
    config.icon = 'wrench'
    return config
  },
  getId : { ->
    return 'aUniqueId'
  },
  sendTo : { report, values, execConfig ->
  	// perform send to action 
  }
  
] as SendToTargetProviderHookAdapter

GLOBALS.services.callbackRegistry.attachHook(HOOK_NAME, SendToTargetProviderHook, callback)

The consumes method takes as input the current report object and returns an object of type SendToClientConfig (or null, if for this report the target should not be active). In its simplest form the SendToClientConfig only takes a title and possibly an icon (any font-awesome icon identifier, you may find the identifiers here: net.datenwerke.rs.theme.client.icon.BaseIcon.SEND) which is used to populate the send-to menu on the client. If the user selects the menu item the sendTo method of the above hook is called. The sendTo method takes as input

report The report object with its current configuration.
values In case a form configuration is specified (we'll cover forms shortly) the values object is a map containing the selected values.
execConfig Contains the execution config which should be passed on in case the report is executed.

An important point to make is that the report object is not necessarily as the stored object in the database but is configured as it was currently configured on the client side. This in particular means that the ID field of the report object is not set. In order to obtain the id you can use the getOldTransientId() method. Note that this is not the case for the report provided to the consumes method.

13.1.1. Output Format

By default the send to target does directly call the script when the user selects the option. You can specify that the user needs to configure an output format. For this call config.selectFormat = true when specifying the SendToClientConfig object. Then, when the user selects the send-to target he or she will first be asked to specify an output format. In this case you can make your life easier when implementing the hook and override the method

public String sendTo(CompiledReport compiledReport, Report report,
			String format, HashMap<String, String> values,
			ReportExecutionConfig... executionConfig)

Here, as first parameter you are given the executed report (compiledReport). In this case we do not need to implement the other sendTo method, and an implementation could look like the following:

import net.datenwerke.rs.core.service.sendto.hooks.SendToTargetProviderHook
import net.datenwerke.rs.core.service.sendto.hooks.adapter.SendToTargetProviderHookAdapter
import net.datenwerke.rs.core.client.sendto.SendToClientConfig

def HOOK_NAME = 'MY_SEND_TO_EMAIL'

def callback = [
  consumes : { report -> 
    def config = new SendToClientConfig()
    config.title = 'Custom Send To'
    config.selectFormat = true
    return config
  },
  getId : { ->
    return 'aUniqueId'
  },
  sendTo : { compiledReport, report, format, values, execConfig ->
  	// perform send to action 
  }
  
] as SendToTargetProviderHookAdapter

GLOBALS.services.callbackRegistry.attachHook(HOOK_NAME, SendToTargetProviderHook, callback)
13.1.2. Form Configuration

In addition to (or in place of) having the user select an output format for the report you can specify additional form fields. That is, you can display a simple form when the send-to menu item is selected and then have the data from the form available when executing the action. This is what the parameter values is for. It contains a map of the form-data specified by the user. In order to configure a form you can set the "form" property of the SendToClientConfig:

def callback = [
  consumes : { report -> 
    def config = new SendToClientConfig()
    config.title = 'Custom Send To'
    config.form = '' // the form configuration via json
    return config
  },
  getId : { ->
    return 'aUniqueId'
  },
  sendTo : { report, values, execConfig ->
  	// perform send to action 
  }
  
] as SendToTargetProviderHookAdapter

The form is defined via JSON (http://www.json.org/). The basic outline is as follows, where ''form'' would take the actual form configuration. The properties width and height define the width and height of the popup window that contains the form.

{
        "width": 400,
        "height": 200,
        "form" : {
                // form configuration
        }
}

The form configuration itself consists of a definition of fields.

        "form" : {
                 "fields": [{
                 	// first field
	         }, {
	                // second field
                 }]
        }

Each field consists of an id, a type, a label and possibly a value. For example,

{
               "id": "email",
               "type": "string",
               "label": "Email Address",
               "value": "name@example.com"
        }

The field id defines a unique name for the form element, label defines the field label and value allows to specify the field's content. Type, defines the type of form field. Currently supported are the types:

string A text field.
int An integer field.
dd A dropdown list.

Putting it all together we could define a form as follows, which consists of three fields, a textfield, an integer field and a dropdown list. Note that the values for the dropdown list are provided as key-value pairs.

{
 	 "width": 400,
        "height": 200,
        "form" : {
                "labelAlign": "left",
                "fields": [{
                        "id": "email",
                        "type": "string",
                        "label": "Email Address",
                        "value": "name@example.com"
                }, {
                        "id": "int",
                        "type": "int",
                        "label": "Some integer field",
                        "value" : "15"
                }, {
                        "id" : "dropdown",
                        "type" : "dd",
                        "label" : "some list",
                        "values" : [
                                {"A" : "B"}, {"c" : "D" }, {"foo" : "bar"}
                        ],
                        "value" : "foo"
                }]
        }
}

If a form is specified, then the values parameter of the sendTo method consist of a Map<String,String> object that contains the value (represented as string) for each form field.

13.1.3. Scheduling

By default any send-to target is also available as a scheduler target. You can prevent this by implementing the method supportsScheduling:

def callback = [
  consumes : { report -> 
    def config = new SendToClientConfig()
    config.title = 'Custom Send To'
    return config
  },
  getId : { ->
    return 'aUniqueId'
  },
  sendTo : { report, values, execConfig ->
  	// perform send to action 
  },
  supportsScheduling: { -> return false }
  
] as SendToTargetProviderHookAdapter

If you have scheduling enabled, then by default a scheduling execution is forwarded to the sendTo method of your hook. You can however further control the scheduling execution by additionally overriding the method scheduledSendTo.

def callback = [
  consumes : { report -> 
    def config = new SendToClientConfig()
    config.title = 'Custom Send To'
    return config
  },
  getId : { ->
    return 'aUniqueId'
  },
  sendTo : { report, values, execConfig ->
  	// perform send to action 
  },
  scheduledSendTo : { compiledReport, report, reportJob, format, values -> 
  	// perform action
  },
  supportsScheduling: { -> return true }
  
] as SendToTargetProviderHookAdapter

Similarly, to the sendTo method you have access to the report object and the configured values. However, in addition you can access the compiledReport (the scheduler executed the report according to the instructions and the first parameter contains the result) as well as to the scheduler job definition.

13.1.4. Complete Example

The following is a complete example that reimplements sending a report via email.

import net.datenwerke.rs.core.service.sendto.hooks.SendToTargetProviderHook
import net.datenwerke.rs.core.service.sendto.hooks.adapter.SendToTargetProviderHookAdapter
import net.datenwerke.rs.core.client.sendto.SendToClientConfig

import net.datenwerke.rs.core.service.mail.MailService
import net.datenwerke.rs.core.service.reportmanager.ReportExecutorService
import net.datenwerke.rs.core.service.mail.SimpleAttachment

def HOOK_NAME = 'MY_SEND_TO_EMAIL'

reportExec = GLOBALS.getInstance(ReportExecutorService)
mailService = GLOBALS.getInstance(MailService)

def callback = [
   consumes : { report ->
      def config = new SendToClientConfig()
      config.title = 'Send via Custom Mail'
      config.icon = 'send'
      config.form = """
{
        "width": 400,
        "height": 180,
        "form" : {
                "labelAlign": "top",
                "fields": [{
                        "id": "email",
                        "type": "string",
                        "label": "Email Address",
                        "value": "name@example.com"
                }]
        }
}
"""
      return config
   },
   getId : {
      ->
      return 'someUniqueId'
   },
   sendTo : { report, values, execConfig ->
      def pdf = reportExec.execute(report, ReportExecutorService.OUTPUT_FORMAT_PDF, execConfig)

      // prepare for sending mail
      def mail = mailService.newSimpleMail()
      mail.subject = 'The Report'
      mail.toRecipients = values['email']
      mail.from = 'from@reportserver.net'

      def attachment = new SimpleAttachment(pdf.report, pdf.mimeType, 'filename.pdf')
      mail.setContent('Some Message', attachment)

      // send mail
      mailService.sendMail mail

      return "Send the report via mail. Config $values" as String
   }

] as SendToTargetProviderHookAdapter

GLOBALS.services.callbackRegistry.attachHook(HOOK_NAME, SendToTargetProviderHook, callback)

The script can be downloaded here: https://github.com/infofabrik/reportserver-samples/blob/main/src/net/datenwerke/rs/samples/tools/sendto/email/sendToEmail.groovy.

If you need scheduling, you can use the following example:

import net.datenwerke.rs.core.service.sendto.hooks.SendToTargetProviderHook
import net.datenwerke.rs.core.service.sendto.hooks.adapter.SendToTargetProviderHookAdapter
import net.datenwerke.rs.core.client.sendto.SendToClientConfig

import net.datenwerke.rs.core.service.mail.MailService
import net.datenwerke.rs.core.service.reportmanager.ReportExecutorService
import net.datenwerke.rs.core.service.mail.SimpleAttachment

def HOOK_NAME = 'MY_SEND_TO_EMAIL'

reportExec = GLOBALS.getInstance(ReportExecutorService)
mailService = GLOBALS.getInstance(MailService)

def doSendEmail(pdf, values) {
  // prepare for sending mail
  def mail = mailService.newSimpleMail()
  mail.subject = 'The Report'
  mail.toRecipients = values['email']
  mail.from = 'from@reportserver.net'

  def attachment = new SimpleAttachment(pdf.report, pdf.mimeType, 'filename.pdf')
  mail.setContent('Some Message', attachment)

  // send mail
  mailService.sendMail mail
}


def callback = [
   consumes : { report ->
      def config = new SendToClientConfig()
      config.title = 'Send via Custom Mail'
      config.icon = 'send'
      config.form = """
{
        "width": 400,
        "height": 180,
        "form" : {
                "labelAlign": "top",
                "fields": [{
                        "id": "email",
                        "type": "string",
                        "label": "Email Address",
                        "value": "name@example.com"
                }]
        }
}
"""
      return config
   },
   getId : {
      ->
      return 'someUniqueId'
   },
   sendTo : { report, values, execConfig ->
      def pdf = reportExec.execute(report, ReportExecutorService.OUTPUT_FORMAT_PDF, execConfig)

      doSendEmail(pdf, values)

      return "Send the report via mail. Config $values" as String
   },
  
   scheduledSendTo: { compiledReport, report, reportJob, format, values ->
     def pdf = reportExec.execute(report, ReportExecutorService.OUTPUT_FORMAT_PDF)
     
     doSendEmail(pdf, values)
   }

] as SendToTargetProviderHookAdapter

GLOBALS.services.callbackRegistry.attachHook(HOOK_NAME, SendToTargetProviderHook, callback)

The script can be downloaded here: https://github.com/infofabrik/reportserver-samples/blob/main/src/net/datenwerke/rs/samples/tools/sendto/email/sendToEmailScheduling.groovy.