Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

[Urgent] How to write re-usable function that can be used by multiple script JQL function's scripts

nallu May 26, 2019

I have written a common function to connect to DB. This function should be used by multiple script JQL functions. 

But it fails to load. Here is what I am doing, 

STEP 1:  Written a class called 'DBUtils' . Inside that class, defined function called  executeQuery(). I have added this script under '

/var/atlassian/application-data/jira/scripts/com/onresolve/jira/groovy/jql' directory. 

import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.ComponentManager;
import groovy.sql.Sql;
import java.sql.Connection;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.DelegatorInterface;
import org.apache.log4j.Logger;

/**
* This script is to connect to JIRA DB and execute the give query
* @param sqlStmt query to be executed
* @return results as list of HashMaps Ex: [ {key, value}, {key, value} ]. Whoever call this function, has to parse the output and fetch the required input based on the given query
*
* @since 27th March, 2019
* @version 1.0
*/

class DBUtils {

def executeQuery(String sqlStmt) {

// Defining arrayList to store result returns by query.
def results = [];

Logger log = Logger.getLogger("Connecting to DB");

if(sqlStmt.split(' ')[0].toLowerCase() == 'select') {

// Connecting to the database.
def componentManager = ComponentManager.getInstance();
def delegator = (DelegatorInterface) componentManager.getComponentInstanceOfType(DelegatorInterface.class);
String helperName = delegator.getGroupHelperName("default");

Connection conn = ConnectionFactory.getConnection(helperName);
Sql sql = new Sql(conn);

// Execute the given query
try {
results = sql.rows(sqlStmt)
} catch (Exception e) {
log.error("Getting error while Script JQL Function trying to connect to DB and execute the query : ${sqlStmt}" + e.getMessage() + "\n");
} finally {
sql.close();
}

}
return results
}

}

 

STEP 2: Have written separate JQL function script and tried using DBUtils class's function (executeQuery()) by creating a object (new DBUtils().executeQuery(query)). But this script is failed to load. 

 

package com.onresolve.jira.groovy.jql;

import com.atlassian.jira.bc.issue.search.SearchService;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.jql.parser.JqlQueryParser;
import com.atlassian.jira.jql.query.LuceneQueryBuilder;
import com.atlassian.jira.jql.query.QueryCreationContext;
import com.atlassian.jira.jql.validator.NumberOfArgumentsValidator;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.util.MessageSet;
import com.atlassian.query.clause.TerminalClause;
import com.atlassian.query.operand.FunctionOperand;
import DBUtils;
import groovy.util.logging.Log4j;


/**
* The ChangedFrom program is implemented to get the list of feature issues which had given value for the given custom field.
* This program will enable history search for the custom fields on feature issue.
*
* @arguments It accepts two arguments.
* Field Name : Name of the custom field
* Changed from value : Value of the custom field
*
* @since 19th March, 2019
* @version 1.0
**/

@Log4j
class ChangedFrom extends AbstractScriptedJqlFunction implements JqlQueryFunction {

// Defining an objects to use JQL query parser
def queryParser = ComponentAccessor.getComponent(JqlQueryParser);
def luceneQueryBuilder = ComponentAccessor.getComponent(LuceneQueryBuilder);
def searchService = ComponentAccessor.getComponent(SearchService);
def applicationUser = ComponentAccessor.getJiraAuthenticationContext().getUser();

/**
* This method is to get the description of the script JQL function.
* This description will be displayed when we search for the ChangedFrom() function in issue navigator in jira application.
**/
@Override
String getDescription() {
"Return feature issues which had the given value for the given custom field"
}

/**
* validate function is to validate the operand and JQL query which is formed based on operand.
* @return It return the error message if operand is incorrect/null
**/
@Override
MessageSet validate(ApplicationUser user, FunctionOperand operand, TerminalClause terminalClause) {
def messageSet = new NumberOfArgumentsValidator(2, getI18n()).validate(operand);

if (messageSet.hasAnyErrors()) {
return messageSet;
}

// Forming the JQL and validating it
def field = '"' + operand.args.first() + '"';
def value = '"' + operand.args.last() + '"';
def queryStr = "${field} = ${value}";
def query = queryParser.parseQuery(queryStr);
messageSet = searchService.validateQuery(user, query);
messageSet
}

/**
* getArguments() function is to define the description and property of the arguments.
* @return argument property as a List<Map>
**/
@Override
List<Map> getArguments() {
[
[
description: "Field Name",
optional: false,
],
[
description: "Changed From Value",
optional: false,
]

]
}

// To define the function name for script JQL function.
@Override
String getFunctionName() {
"changedFrom"
}

/**
* getQuery function is to get the query context which is entered in issue navigator, perform defined logic/operation to get deseried outcome,
* form outcome and return the JQL result
*
* @return list of feature issues in issue navigator in JIRA application
**/
@Override
Query getQuery(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {

//log.setLevel(org.apache.log4j.Level.DEBUG); //Uncomment this code to enable debugging logs

// Grep custom field name and it's value from operand
def fieldName = operand.args.first().toString();
def value = operand.args.last().toString();

//log.debug("Field Name = ${fieldName} - - - Value = ${value}"); //Uncomment this code to enable debugging logs

// Defining arrayList to store feature keys.
def featureLinkList = [];

// SQL query to fetch value from db
def sqlStmt = "select some query";

if(sqlStmt.split(' ')[0].toLowerCase() == 'select') {

// Connecting to JIRA DB and execute given query
def dbUtils = new DBUtils();
def temp = dbUtils.executeQuery(sqlStmt);
temp.each {
featureLinkList.add("${it.featurekey}");
}

}

//log.debug("SQL Query : ${sqlStmt}"); //Uncomment this code to enable debugging logs

/**
* Framing the Query(using the data fetched from DB) to fetch Feature issues through JQL
* if the SQL doesn't return anything, then it will return 'No Issues found'
*/
if(featureLinkList)
{
def queryTemp = featureLinkList.unique().join(',').toString();
def secondQuery = "key in (" + queryTemp + ")";
def secondquery = queryParser.parseQuery(secondQuery);
luceneQueryBuilder.createLuceneQuery(queryCreationContext, secondquery.whereClause);
}
else
{
def secondQuery = "key in (FEATURE-0)";
def secondquery = queryParser.parseQuery(secondQuery);
luceneQueryBuilder.createLuceneQuery(queryCreationContext, secondquery.whereClause);
}
}

}//End of the script;

 

What am i doing wrong?  Can anyone pls help me? 

2 answers

0 votes
Matthew Clark
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2019

Hi @nallu ,

I have diagnosed your "changedFrom.groovy" script and can see the following problems:

1- Your class name is different from the file name (Case sensitive). These must be the same as recommended in Java standards.
2- You are missing the following import in the changedFrom file:

import org.apache.lucene.search.Query

Please refer to this guide section to see where we show this import being used. (This is probably the main cause of your problem)

3- I cannot see your package structure but make sure the ChangedFrom class can find your DBUtils class

 


 

I would advise you do not allow user input to be directly interpolated into a SQL query. This is how you would become susceptible to SQL injection attacks.

See these links here:
https://www.owasp.org/index.php/SQL_Injection
https://www.w3schools.com/sql/sql_injection.asp

You should use a Prepared Statement to define your SQL before it is executed. See here for why and how you should use Prepared Statements.

Regards,
Matthew

nallu May 30, 2019

Hi @Matthew Clark , Thanks so much for taking a look. 

When I keep both file name and class name same as DBUtils, It throws below exception

root@devjira100:~> tail -f /var/atlassian/application-data/jira/log/atlassian-jira.log

            DBUtils obj = new DBUtils();

            ^

    

    file:/var/atlassian/application-data/jira/scripts/com/onresolve/jira/groovy/jql/changedFrom.groovy: 36: reference to DBUtils is ambiguous, both class DBUtils and DBUtils match

     @ line 36, column 24.

            DBUtils obj = new DBUtils();

                              ^

    

    2 errors

 

Matthew Clark
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2019

Hi @nallu 

Have you changed the scripts at all because that line number 36 does not correspond to the scripts I have seen?

When I mentioned changing the class name I meant just change the script file called changedFrom.groovy to match the class name ChangedFrom.

Can you show me the full script that presents this new error?

nallu May 30, 2019

Hi @Matthew Clark , 36 line is where i m creating an object for DBUtils class. But I haven't explicitly import the DBUtils class in ChangedFrom script since both resides in same directory.  Do I have to? If so, How should i do it?

 

Here is a snippet of the code, 

      1 package com.onresolve.jira.groovy.jql;

      2

      3 import com.atlassian.jira.bc.issue.search.SearchService;

      4 import com.atlassian.jira.component.ComponentAccessor;

      5 import com.atlassian.jira.ComponentManager;

      6 import com.atlassian.jira.jql.parser.JqlQueryParser;

      7 import com.atlassian.jira.jql.query.LuceneQueryBuilder;

      8 import com.atlassian.jira.jql.query.QueryCreationContext;

      9 import com.atlassian.jira.jql.validator.NumberOfArgumentsValidator;

     10 import com.atlassian.jira.user.ApplicationUser;

     11 import com.atlassian.jira.util.MessageSet;

     12 import com.atlassian.query.clause.TerminalClause;

     13 import com.atlassian.query.operand.FunctionOperand;

     14 import groovy.sql.Sql;

     15 import groovy.util.logging.Log4j;

     16 import java.sql.Connection;

     17 import org.apache.lucene.search.Query;

     18 import org.ofbiz.core.entity.ConnectionFactory;

     19 import org.ofbiz.core.entity.DelegatorInterface;

     20

     21 /**

     22 * The ChangedFrom program is implemented to get the list of feature issues which had given value for the given custom field.

     23 * This program will enable history search for the custom fields on feature issue.

     24 *

     25 * @arguments It accepts two arguments.

     26 *    Field Name         : Name of the custom field

     27 *    Changed from value : Value of the custom field

     28 *

     29 * @since    19th March, 2019

     30 * @version  1.0

     31 **/

     32

     33 @Log4j

     34 class ChangedFrom extends AbstractScriptedJqlFunction implements JqlQueryFunction {

     35

     36     DBUtils obj = new DBUtils();
Matthew Clark
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2019

 

I think that is because you are no longer importing your custom DBUtils class in your list of imports at the top of your file.

It was in your original so you must have accidentally deleted it.

Update:

I misread the last part of your update. Yes, you do need to import the DBUtils class. If the DBUtils.groovy file and the "ChangedFrom.groovy" file are in the root directory <JiraHome>/scripts/ then you can import it the same way you did in the first "ChangedFrom.groovy" script you sent us.

import DBUtils

But if you wanted to put the util class in a sub directory you can. We show an example of this here.

 

0 votes
Andrew
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 26, 2019

Hi @nallu ,

Better ask here https://community.developer.atlassian.com/ .

I not sure what mean 'script is failed to load'. Could You please give logs?

B.R.

nallu May 27, 2019

Hi @Andrew , Thanks for looking into it. So, After adding the object creation step in 2nd script 

def dbUtils = new DBUtils(); 

 The JQL function getting removed from the JIRA application while scanning the JQLs here ->

Screen Shot 2019-05-27 at 2.11.20 PM.png

Suggest an answer

Log in or Sign up to answer