Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 14 Next »


Intro

Macros are basically long instructions that users can apply to save time on repetitive tasks like long input sequences of keyboard and mouse actions or reusing code. Macros take the form of programming scripts. In XTRF, they're most commonly used to export data, modify custom field values, or alter database entities; however, the possibilities are virtually unlimited.

XTRF uses Apache Groovy as a programming and scripting language for macros. Powerful, optionally typed, and dynamic, it's best compatible with the Java platform with its static-typing and static compilation capabilities, as well as concise, familiar, and easy-to-learn syntax.

For full Groovy macro documentation, click HERE.


Macros settings in XTRF

To manage existing macros and add new ones, go to the (blue star) Configuration menu > Integration > XTRF Macros.

Check the Enable XTRF Macros box to allow using macros on the XTRF Platform.

Add a macro

To create a new macro, perform the following steps:

  1. Click the Add button on top of the XTRF Macros table.

  2. In the Options section:

    1. From the Class Name drop-down, select the scope of the macro (where it will be applicable, e.g., Project, Job, Vendor Payment, etc.)

    2. Name your macro.

    3. Decide whether it should be Active.
      (info) Inactive macros won't appear on the views.

    4. For a macro that generates an output file:

      1. Check the Macro Generates Output box.

      2. Provide the Filename with an extension for the generated file, e.g., Output.csv.

      3. Select the file Encoding.

      4. (Optional) From the End of Line drop-down, select the line break type to be used in the output file.

    5. In the Maximum Number of Items field, limit the number of entities for which this macro can be run simultaneously.

    6. Check the Macro Modifies Model Data box if you want the macro to modify data in the system, e.g., change job assignments.
      (blue star) Leave it unchecked if you only want to export data or create reports.

  3. In the Editor section:

    1. (Optional) Click the (blue star) icon to expand and configure the Editor Options.

    2. Provide the actual code of the macro.

    3. (Optional) Click the (blue star) icon to preview your output or return value.

  4. Click the Save or Save and Exit button.


Test a macro

To test a macro, perform the following steps:

  1. Open the macro in question in edit mode.

  2. In the Editor section, expand the (blue star) Editor Options.

  3. In the Test Objects field, start typing the name of the entity on which you want to test the macro.
    (blue star) You can select several test objects.

  4. In the Test cases section, click the Add Test Case button. The Add new Test Case pop-up appears.

  5. Name the test case and click the Save button. The test case appears on the list.

  6. Click the (blue star) icon to run the test case.

If the macro works, you'll get a success flash message. If there are some problems with the macro, the Test Result pop-up window appears.


Limit access to a macro

By default, new macros are available for all user groups. However, you may want to limit access to certain macros to eliminate the potential risk of errors and system malfunctions. To do so, perform the following steps:

  1. Open the macro in question in edit mode.

  2. Go to the Permissions section.

    1. Uncheck the All Groups box.

    2. Select the groups that will have access to this macro by double-clicking on a group in the Available Items list.

  3. Click the Save or Save and Exit button.

Now the macro can be seen and run only by the selected user groups. Users from excluded user groups won't see the macro on the list and won't be able to run it, even through the API.

User groups with 'Edit' rights to macros can still browse and modify all macros through the configuration menu. This includes changing the permissions to run macros.


Run a macro

Macros can be run from the Smart views corresponding to the macro's classes (Clients, Vendors, Projects, Invoices, etc.).

To run a particular macro, perform the following steps:

  1. Go to the browsing view of the class you want to run the macro for (e.g., Clients module > Clients > All Clients view.).

  2. Check the boxes to select the specific items.

  3. In the menu above the browsing list, click the Macros drop-down menu and select the macro you want to run.

If there are no macros for the desired class on your XTRF platform, the Macros drop-down menu will not appear in this class' browsing view.


Apache Groovy basics

 Click to expand
  • Groovy scripts are considered a function.

  • In Groovy, the very last statement of the function is considered a return value.

  • The return value will be the XTRF Macro output.


Declaring and using variables

Groovy lets the users define their own variables. Variables can be typed (as in Java) or untyped.

While typed variables have a predetermined data type, which may disallow certain assignments (e.g., one cannot assign a Date object to a BigDecimal variable), they have one advantage over untyped variables - they allow more control over what and how is processed by the code.

Only base Java Classes can be used as typed variables without typing anything else - the rest has to be imported first.

For example, to use a Quote class, enter:

import com.radzisz.xtrf.model.quote.Quote
Quote quote

The full list of classes used in XTRF can be found in JavaDoc documentation.

To declare a variable, simply enter its type (in case of typed variables) or use a special keyword def (untyped variables). After a variable is declared, it can be used by typing its name.

def hello = "hello world"
hello

Variables can be used inside a text in quotation marks.

The text must be entered in quotation marks to interpret a variable - if the text is entered in apostrophes, variables will not be evaluated.

Groovy allows the use of multiline Strings.


Closures

One of the most powerful tools in Groovy is closures. A closure in Groovy is an open, anonymous block of code that can take arguments, return a value, and be assigned to a variable.

Closures are defined by brackets.

To call a closure assigned to a variable (named closure in the example above), type its name and parameters. Closure parameters should be put between the parentheses.

Parameters are optional - this example closure simply increases the variable by one each time the closure is called.

New variables can be defined inside a closure. Those variables will be visible only inside a closure and cannot be used outside of it.

Groovy also has some built-in useful closures to run on collections.

each{element -> }

Iterates through all collection items.

eachWithIndex{element, index -> }

Iterates through all collection items and their indexes.

Please note that the order in which parameters are named matters - the first is always the element of the collection, and the second is always the index.

find{ element -> element == “Hello world”}

Returns the first element matching the criteria.

findAll{ element -> element >= 100}

Returns all elements matching the criteria.

collect{ element -> element * 100}

Converts each element into a new value using the closure as the transformer. Returns a List object.

Further information on closures can be found at https://groovy-lang.org/closures.html


Loops and checking conditions

Loops in Groovy can be done in two basic ways:

For loop works exactly like in C++, Java, and other programming languages:

for(variable declaration ; expression ; increment){
  //code inside a loop
}

variable declaration

Executed only once for the entire loop and used to declare any variables that will be used within the loop.

expression 

Consists of an expression that is evaluated for each iteration of the loop. If the expression is logically false (e.g., 2 > 3 ), the loop will end and not enter in the code written in brackets.

increment 

Contains the logic needed to increment the variable declared in the for statement.

The second way to create a loop is a while loop. Its structure is simpler than the for loop, as it only consists of the condition and the code.

while(expression){
  //code
}

The while loop will be running as long as the condition set in the expression part is true. This means that if the above example did not have the incrementation of the i variable (i++ in line 5), the loop would run indefinitely! (as 0 is always less than 10).

Both loops can also use two control statements:

  • break, which stops further execution of the loop (even if the condition is true and the loop would normally be executed again):

  • Continue, which skips the rest of the code in the current iteration of the loop only:

    Please note that if the i variable was not iterated at line 5, the while loop would be endless, as i would remain there without further incrementation!

    The same result using the for loop:

The if used above is the example of checking a condition. The structure is very simple:

if(condition){
  //code
}
else{
  //code
}

The else part is optional: it is run only when the condition specified in the if condition is not met.


The macro template

This is a universal structure for XTRF macros.

At the beginning of each macro, you need to import the necessary classes that can be found in the JavaDoc documentation.

import com.radzisz.xtrf.utils.velocity.VelocityTagUtils

class ${NAME}Macro {
  def list
  def params
  VelocityTagUtils utils = new VelocityTagUtils()
  
  ${NAME}Macro(list, params) {
    this.list = list
    this.params = params
  }
  def runMacro() {
    return 0
  }
}
new ${NAME}Macro(list, params).runMacro()
  • ${NAME} is the custom name of your macro.

  • All the actual code goes into the runMacro() method.

  • The variable utils contains some useful methods - you can check them out in the Javadoc in VelocityTagUtils.


XTRF macros best practices

These are the rules and templates the XTRF Customization Team sticks to (and we advise you to do so as well to make your coding easier).

The macro code is separated into main parts:

  • Main macro class

  • Execution phase

Such a structure is designed for the macro to run automatically whenever started in XTRF (will be run as a standard Groovy script) and to allow testing (by using OO elements).

General rules

  1. Macro must be executed by the runMacro() method (without any parameters).

  2. Whatever is returned by runMacro() is considered the macro output.

  3. Every macro class must have a constructor with exactly two parameters: list (the list of processed entities) and params. Names in the class itself can be changed. Those names cannot be changed in the execution line (see below).

  4. The execution line new GetReceivablesMacro(list, params) .runMacro() must be present in every macro.

Object Oriented Programming

You can use the benefits of object-oriented programming while writing your macros. You can create as many classes and functions as you need, but be aware that they need to be compiled into a single script before being added to XTRF.

List and Params

You saw two variables defined in the macro template - list and params.

  • List will be the list of items you run the macro on. If you select three projects from the view and run the macro on them, the list object in that macro will be of the type: List<com.radzisz.xtrf.model.project.Project>.
    You can then iterate over the list in the code of the macro.

  • Params is for the more advanced users - it is most useful when running a macro from the XTRF Home Portal API. You can execute the Run Macro POST request that contains this JSON:

    {
      "params": {
      "id": "848"
      }
    }

    And then, in the macro:
    params.id == 848
    Will return true.
    You can pass a map of as many parameters as you want.


XTRF macro examples

Generate a CSV file

 Click to expand

Let's say that you want to export the clients' details. You want their names (legal name), email addresses, and tax numbers, all in a .csv file.

This is how your macro options should look like:

The Macro Modifies Model Data box is not checked, as you will not change any of the client's data.

Preparing the CSV

CSV files are text files that follow a special format: their values are separated by a separator.

You want your file to have headers with the names of the fields and then the list of values.

We need to create a framework for these elements. You can also use any existing Java framework, but for this simple example, it will not be necessary.

The Tuple class from the Groovy API is a good idea to use here. A tuple is like a list: it has a stated number of elements, and so does a CSV file, i.e., if there are four headers, each line needs to have four elements.

Our CSV file will have three columns:

  • Name

  • Email

  • Tax Number

Let's set our separator as a semicolon and type out the header.

import com.radzisz.xtrf.utils.velocity.VelocityTagUtils
import com.radzisz.xtrf.model.partner.customer.Customer

class ExportClientsMacro {
  def list
  def params    
  VelocityTagUtils utils = new VelocityTagUtils()
    
  ExportClientsMacro(list, params) {
  this.list = list
  this.params = params
  }

  def runMacro() {
    String separator = ";"
    Tuple header = ["Name", "Email", "Tax Number"]
    List<Tuple> lines = []
    return 0
  }
}
new ExportClientsMacro(list, params) .runMacro()

We've also created a list of lines we will start filling out in the next step.

Getting the data and filling the lines

You know that the list variable contains the list of Customers.

We will be iterating over the list and filling one line at a time; for this, the each method will be the best choice.

def runMacro() {
    String separator = ";"
    Tuple header = ["Name", "Email", "Tax Number"]
    List<Tuple> lines = []
    list.each { Customer customer ->
    }
    return 0
  }

In the Customer class, there are get() methods for getting all of the required fields.

Here's the method to get the full name:

Let's get all these fields and assign them to variables inside the each closure.

Now that we have the data, let's put it in a Tuple3 (size of 3 - tuples have their size included in their class name) and add it to the list.

def runMacro() {
    String separator = ";"
    Tuple header = ["Name", "Email", "Tax Number"]
    List<Tuple> lines = []
    list.each { Customer customer -> 
      String name = customer.getFullname()
      String email = customer.getEmail()
      String taxNumber = customer.getTaxNo1()
      Tuple line = [name, email, taxNumber]
      lines.add(line)
    }
    return 0
  }

Now, the macro will do that for each customer in the list, and we will only need to connect the header with the lists and generate the output string.

Let's write a method to do just that and call it createCsv. It will return a String that will be the final product of the macro, and it will take the header, the list of lines, and the separator as parameters.

static String createCsv(Tuple header, List<Tuple> lines, String separator) {
  String preparedHeader = header.join(separator)
  String preparedLines = lines.collect { it.join(separator) }.join("\n")
  return preparedHeader + "\n" + preparedLines
}

Here, we use the join method to transform lists into strings, which now fit the .csv format since they are separated with semicolons.

For the lines - we use the collect method, which transforms each entry (here, we transformed each line tuple to join it with semicolons), and then we use the join method again to separate the lines with the newline sign.

We can now use this method to get the return value of the macro.

def runMacro() {
    String separator = ";"
    Tuple header = ["Name", "Email", "Tax Number"]
    List<Tuple> lines = []
    list.each { Customer customer -> 
     String name = customer.getFullname()
      String email = customer.getEmail()
      String taxNumber = customer.getTaxNo1()
      Tuple line = [name, email, taxNumber]
      lines.add(line)
    }
    return createCsv(header, lines, separator)
}
static String createCsv(Tuple header, List<Tuple> lines, String separator) {
  String preparedHeader = header.join(separator)
  String preparedLines = lines.collect { it.join(separator) }.join("\n")
  return preparedHeader + "\n" + preparedLines
}

Now paste the entire code into the macro editor in XTRF and start testing!

Modify a project

 Click to expand

In this example, we will change the dates of a project.

We want to change all of the tasks' deadlines to tomorrow.

Here is the setup:

Both boxes are now checked since we want the macro to modify the data, and we will use an output to inform us whether the macro was executed successfully and what has been changed.

We will start with the empty template. We know that the list will contain the list of projects selected from the view. We need to get to the tasks, and as projects can have multiple tasks, we need to iterate over projects as well as tasks. For this, we will use the getTasks() method on the Project class.

import com.radzisz.xtrf.model.project.Task

def runMacro() {
  list.each { Project project ->
    project.getTasks(). each { Task task ->
    }
  }
  return 0
}

Then we need to calculate the date we want to change the deadline to and set it. We can also add the information about this task to the output.

import com.radzisz.xtrf.model.project.Project
import com.radzisz.xtrf.model.project.Task

def runMacro() {
  List<String> output = []
  list.each { Project project ->
    project.getTasks(). each { Task task ->
      Date tomorrow = new org.joda.time.LocalDateTime().now().plusDays(days: 1).toDate()
      task.getDateBoundaries().setDeadline(tomorrow)
      output.add("Set deadline to ${tomorrow} for task with id ${task.idNumber}")
    }
  }
  return output.join("\n")
}

For the output, we created a list of strings, which we then joined with newlines so that each log was in a separate line.

You can now run the macro on projects, and the output will be displayed.

  • No labels