I am working on two parts of a program. One that creates small groovy scripts and another that executes them in a Job. Each script is used to convert information from a map to a Domain object. The job will then save the domain object for future use.
Here is a small example.
Domain
class Report {
Date date
Country country
}
Map
Map<String, String> map = new HashMap<String, String>();
map.put("date", "2015-04-21 11:31:11");
map.put("country", "United States");
Date Script
String script = "x.date = Date.parse('y-M-d h:m:s', y['date'])"
The script is currently executed using Eval.
Eval(report, map, script)
There are other more complicated scripts that need to look up information and make decisions based on values.
The date script works fine but when the country script executes I get an error.
Country Script
String script = "import app.Country\nx.country = Country.findByName(y['country'])"
Error
Script1.groovy: 1: unable to resolve class app.Country
It seems like the Country class is not getting loaded in the call to Eval. How can I do an import in Eval? Should I use Eval or GroovyShell?
You need to instantiate a GroovyShell passing as argument the grailsApplication class loader, see the example below:
Bootstrap.groovy
Map bindingValues = new HashMap()
bindingValues.sessionFactory = sessionFactory
bindingValues.propertyInstanceMap = propertyInstanceMap
bindingValues.dataSource = dataSource
bindingValues.ctx = grailsApplication.mainContext
GroovyShell gs = new GroovyShell(grailsApplication.classLoader, new Binding(bindingValues))
gs.evaluate new File("docs/CargaAvaliacoes.groovy")
CargaAvaliacoes.groovy
import avaliacao.Colaborador
import modelo.ModeloAvaliacao
import programa.Programa
def programa = Programa.get(1)
def modelo = ModeloAvaliacao.get(1)
def avaliadores = ["02270789332":"1020016","11388449681":"1010002","02231772331":"1010004","04247774332":"1020002"]
def avaliacaoService = ctx.getBean("avaliacaoService")
avaliadores.each {
def avaliador = Colaborador.findByCpf(it.key)
def avaliados = Colaborador.findAllBySetorAndCpfNotEqual(it.value,it.key)
avaliados.each {
avaliacaoService.cadastrar(programa, modelo, avaliador, it)
}
}
** You need to specify the grailsApplication.classLoader because this class loader is used to load the Grails domain classes.
Related
I have a Jenkins shared library.
I'm loading a groovy resource file which looks like this:
return [${Constants.nodeIP}#${Constants.username}]
The Constants class:
public class Constants{
public static String nodeIP = "10.0.0.1"
public static String username = "barel"
}
My purpose is to load the resource file into String and also subtitute the $ signs with the actual value.
import Constants
def template = libraryResource resource: 'myTemplate.txt'
def finalResult = """
more text goes here
$template
and here
"""
println(finalResult)
Expected output is:
more text goes here
return [10.0.0.1#barel]
and here
But I'm getting:
more text goes here
return [${Constants.nodeIP}#${Constants.username}]
and here
The main point is that i need to pass the template through the template engine in order to substitute the values but for that I need to create an implicit mapping object and provide it to the template:
def engine=new groovy.text.SimpleTemplateEngine()
def templateEngine = engine.createTemplate(template)
def binding = [ "nodeIP" : "10.0.0.1", "username" : "barel"]
templateEngine.make(binding)
I don't want to re-declare all fields when I'm using the template.
Is there any way to get a reference to the defaultive/built-in TemplateEngine which already has access to all project fields and properties?
I'm a groovy novice, so not sure if this is possible. I need something like an import, but not quite. Here is the simplified code example of what I'm trying to do:
in file FileToProcess.groovy I have
class FileToProcess implements Serializable {
String FileName
FileToProcess(fileName) {
this.FileName = fileName;
}
}
and then in a JenkinsFile I want to do something like
F1 = new FileToProcess('A');
List<FileToProcess> allFiles = new ArrayList<FileToProcess>();
allFiles.add(F1);
for (FileToProcess file : allFiles) {
System.out.println(file.FileName);
}
Now, on StackOverflow I've found examples of how to instantiate a class from another file, for example here or here, and that solves the line
F1 = new FileToProcess('A');
but it does not show how to use that class as a type in a declaration, for example
List<FileToProcess> allFiles = new ArrayList<FileToProcess>();
gives me "unable to resolve class FileToProcess". I also know that using a class as a type like this should work, because it does when I put the class in the same JenkinsFile, so the problem seems to be just that the class is not visible in the JenkinsFile.
Is there a way to do this?
I'm not sure how you tried to setup your class libraries but I attempted the same while using a class from a pipeline shared library and it worked as intended
https://jenkins.io/doc/book/pipeline/shared-libraries/
I did do it a little differently:
assuming you places the file under src/org/Foo.groovy
import org.Foo
def list = new ArrayList<Foo>
list.add(new Foo('str1'))
list.add(new Foo('str2'))
I have a pyramid application that uses a translation factory defined in this way:
from pyramid.i18n import get_localizer, TranslationStringFactory
from pyramid.threadlocal import get_current_request
def add_renderer_globals(event):
request = event.get('request')
if request is None:
request = get_current_request()
event['_'] = request.translate
event['localizer'] = request.localizer
tsf = TranslationStringFactory('climmob3')
def add_localizer(event):
request = event.request
localizer = get_localizer(request)
def auto_translate(string):
return localizer.translate(tsf(string))
request.localizer = localizer
request.translate = auto_translate
It works fine, however somewhere else I use jinja2 render() function to render small pieces of reusable code (snippets) as a jinja2 extension:
from jinja2 import Environment
jinjaEnv = Environment(extensions=['jinja2.ext.i18n'])
output = template.render(snippetVars=kw,renderer='snippet')
The problem here is that when I use the '_' translation function in the template code I get:
UndefinedError: 'gettext' is undefined
I saw some posts that maybe I need to use jinjaEnv.install_gettext_translations() but I cannot make it work. I tried:
jinjaEnv.install_gettext_translations(pyramid.il8n)
jinjaEnv.install_gettext_translations(tsf)
How can I integrate jinjaEnv.install_gettext_translations() with my pyramid translation factory?
Depending on your exact case, you could use pyramid_jinja2 or get inspiration from it. It creates a GetTextWrapper https://github.com/Pylons/pyramid_jinja2/blob/28944ce627745691ccd1603c56251e038aadd892/pyramid_jinja2/i18n.py that makes its way in the options passed when creating the Environment https://github.com/Pylons/pyramid_jinja2/blob/28944ce627745691ccd1603c56251e038aadd892/pyramid_jinja2/settings.py#L133
https://github.com/Pylons/pyramid_jinja2/blob/28944ce627745691ccd1603c56251e038aadd892/pyramid_jinja2/__init__.py#L394
https://github.com/Pylons/pyramid_jinja2/blob/28944ce627745691ccd1603c56251e038aadd892/pyramid_jinja2/__init__.py#L404-L405
The wrapper is needed because the localizer will change every request, depending on the user locale.
Or you can pass the gettext and ngettext arguments directly when you render. In you case, it would look something like:
localizer = request.localizer
def gt(message):
return localizer.translate(message, domain='your-domain')
def ngt(singular, plural, n):
return localizer.pluralize(singular, plural, n, domain='your-domain')
output = template.render(
snippetVars=kw,
renderer='snippet',
gettext=gt,
ngettext=ngt,
)
In my Jira project I have a custom field called "CASE-Environment". Its a Select List(Multiple values)
I would like this custom field to have values dynamically populated. In my actual project, I am making use of RESTFul calls wherein I get the actual values from an internal RESTFul service. But for demo purposes I am showing the values here as hard coded via an initializer.
I am making use of the Script Runner Jira plugin which lets me define behaviors, associate it with fields. I have the following in the initializer section of the behavior.
import com.onresolve.jira.groovy.user.FieldBehaviours
import com.onresolve.jira.groovy.user.FormField
Map fieldOptions = [:]
fieldOptions.put("-1","None")
fieldOptions.put("100","Dev");
fieldOptions.put("101","Staging");
fieldOptions.put("102","QA");
fieldOptions.put("103","Production");
FormField env = getFieldByName("CASE-Environment");
env.setFieldOptions(fieldOptions)
I am successfully able to see this values on the UI when I am trying to create an issue.
But when I am trying to submit the issue, I hit a Jira validation error message which reads as below
Invalid value '102' passed for customfield 'CASE-Environment'
I am guessing that Jira somehow is not recognizing the dynamically added option values for my SelectList [ multiple values ]
I even tried building a mapping for this field wherein my behavior would get triggered everytime the custom field CASE-Environment under went changes.
The mapping code is shown as below :
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.customfields.option.Options
import com.atlassian.jira.issue.customfields.option.Option
import com.onresolve.jira.groovy.user.FieldBehaviours
import com.onresolve.jira.groovy.user.FormField
def adddOption = {
Issue issue,
CustomField customField,
String value ->
Options l = ComponentAccessor.getOptionsManager().getOptions(customField.getRelevantConfig(issue))
int nextSequence = l.isEmpty() ? 1 : l.getRootOptions().size() + 1;
Option newOption = ComponentAccessor.getOptionsManager().createOption(customField.getRelevantConfig(issue), null, (Long)nextSequence, value);
return newOption;
}
FormField environment = getFieldById(fieldChanged)
Issue issue = getUnderlyingIssue()
MutableIssue mutableIssue = ComponentAccessor.getIssueManager().getIssueObject(issue.id);
CustomField field = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("CASE-Environment")
List<Option> allOptions = new LinkedList<>();
List<String> chosenValues = (List<String>) mutableIssue.getCustomFieldValue(field);
for (String eachValue : chosenValues) {
allOptions.add(adddOption(issue, field, eachValue));
}
mutableIssue.setCustomFieldValue(field, allOptions);
But even after this I still can't get past the error message. Can someone please help me with getting this sorted out ?
For what its worth, I am using Jira : JIRA v6.3.5
So after toying around for quite sometime on this, I finally managed to crack this on my own.
The below mentioned initializer section should help get this sorted out easily.
import com.atlassian.jira.component.ComponentAccessor
def optionsManager = ComponentAccessor.getOptionsManager()
def fieldManager = ComponentAccessor.getCustomFieldManager()
def envFld = fieldManager.getCustomFieldObjectByName("CASE-Environment")
def fieldConfig = envFld.getRelevantConfig(getIssueContext())
def newSeqId = 0
//For the sake of e.g., I am using a initialized array. In real life one can
// construct this by querying a DB or hitting a RestFul service etc.,
def envs = ["QA", "Staging", "Dev", "Production"]
def issueService = ComponentAccessor.getIssueService()
def issueInputParameters = issueService.newIssueInputParameters()
for (String env : envs) {
def option = optionsManager.createOption(fieldConfig, null, newSeqId++, env)
issueInputParameters.addCustomFieldValue(envFld.idAsLong, option.optionId.toString())
}
Thanks #Krishan
just one comment, new JIRA 7/ScriptRunner have problem with static type check. Replace the definition of "newSeqID"
def newSeqId = 0
with
Long newSeqId = 0
Grails 1.3.7
Trouble with data binding Command objects that have List content. Example Command:
class Tracker {
String name
String description
List<Unit> units = new ArrayList()
}
class Unit {
String name
Long unitMax
Long unitMin
}
create GSP for Tracker has the Unit fields. One example:
<g:textField name="units[0].unitMax" value=""/>
TrackerController save method:
def save = { Tracker trackerInstance ->
trackerInstance = trackingService.saveOrUpdateTracker(trackerInstance)
}
But, always java.lang.IndexOutOfBoundsException
Alternatively, if I update controller to:
def save = {
Tracker trackerInstance = new Tracker()
trackerInstance.properties = params
....
Then groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: properties for class: com.redbrickhealth.dto.Tracker
Any ideas?
There seems to be a difference between binding in GORM vs Command objects.
Maybe I need to extend and register a PropertyEditorSupport for Unit?
-Todd
Since Groovy 1.8.7 the List interface has a method called withLazyDefault that can be used instead of apache commons ListUtils:
List<Unit> units = [].withLazyDefault { new Unit() }
This creates a new Unit instance every time units is accessed with a non-existent index.
See the documentation of withLazyDefault for more details. I also wrote a small blog post about this a few days ago.
Grails requires an command with existing list, that will be filled with data from reques.
If you know exact number of units, say 3, you can:
class Tracker {
String name
String description
List<Unit> units = [new Unit(), new Unit(), new Unit()]
}
or use LazyList from apache commons collections
import org.apache.commons.collections.ListUtils
import org.apache.commons.collections.Factory
class Tracker {
String name
String description
List<Unit> units = ListUtils.lazyList([], {new Unit()} as Factory)
}