As a Java developer I have spent a lot of time trying to understand groovy's closers in order to learn Gradle. Thx you a lot for this and groovy DSL tutorials. It helped me so much!!
There is even a shorter way to build your expected output: data.countBy { it } I'm gonna record a video about interesting collection methods in Groovy soon. Thanks for helping me in crafting this idea. Have a good day!
Hello @Szymon Stepniak, how can we pass specific elements to the closure say for example we want to pass 20 elements at a time from the map and pass it to the closure. We want to have sleep/wait after every 20 elements passed. The map has around 1000 elements.
Hi, Yash! Thanks for the comment! Could you share some code snippet or something, so I can better understand what you are trying to achieve? Are you trying to chunk a map (or a list), iterate it, and call some closure with arguments? Explaining your use case with an example would be most efficient to understand your problem better. I will do my best to help you.
@@szymonstepniak following is the line of code: Script { def appnamelist = params.APP_NAME.tokenize(',') def parts = [:] appnamelist.each { name -> parts[name] = { stage(){} stage() {} stage() {} } } } parallel parts } In the parts[name] I am getting 1000 elements I want to control the flow of elements such that only 50 elements are passed to parts[name] Could you let me know what can be done for it.
@@themastermindisyash I don't know what exactly do you expect from this pipeline part, but it looks broken to me. If you put multiple stages inside the single parallel stage, Blue Ocean UI won't be even able to render them correctly and you will end up seeing only the first (out of three) stage from each part[name] parallel stage. I tried to recreate your example with chunked input (using Groovy's Collection.collate(num) method that chunks a list to a list of lists of size num) to slice an input list of 100 elements into a list of lists of size 10, so I can create the "parts" map ten times with 10 elements only (a closure with 3 stages that just echo the name of the app), and then I can run "parallel parts" so it triggers 10 parallel stages (that contain 3 sequential stages), and it killed my Jenkins. I run this experiment only to show you that this is not the right way to utilize parallel stages in the Jenkins pipeline. Here is the code I run on my Jenkins, I also attached the screenshot of Blue Ocean UI that couldn't render those sequential stages in each parallel stage: gist.github.com/wololock/acce19567af677a535337a7408d2e6ac I would suggest taking a step back and rethinking what you are trying to achieve. This input of 1,000 app names sounds suspicious and it suggests that you are trying to utilize a single Jenkinsfile to build 1,000 apps in parallel, where every parallel stage triggers three sequential stages. This is wrong, this is going to kill your Jenkins server.
@@szymonstepniak Then which other approach can we adapt inorder to achieve the objective. I have to provision 1000 application with same set of stages for each one. Can you suggest a better approach here which can help achieve the objective?
@@themastermindisyash Well, the truth is I don't know what your objective is. I can only guess based on your question, which does not provide much information. I don't know what provisioning application means, and why you can't do it in the dedicated Jenkinsfiles. However, what I'm guessing is you want to parallel common steps and you want to split huge input between those parallel steps. The only thing that comes to my mind is something like this: gist.github.com/wololock/3aeb2bdb48bf4ee1f57aada58ee8f6d0 It requires setting two Jenkins pipelines job. The first one (called Jenkinsfile-A.groovy in the attached gist) takes the input (1,000 app names) and it chunks this input to the desired size (I used chunk size 20 in the attached example). Then it iterates those chunks and for each chunk, it triggers the second pipeline job (named "parallel-job-name", you need to use a real pipeline job name instead) with the parameter APP_NAME and a value that represents only this chunk. It will trigger the job with the next chunk only when the previous job is done (you can change it by setting "wait" to false.) The second Jenkins pipeline (called Jenkinsfile-B.groovy in the attached gist) takes the chunked input and it runs those 3 stages (or any number of stages) and it parallelizes each stage for every value taken from the APP_NAME parameter. As you can see, it uses a "parallel" step in every stage, and this is on purpose - this way you can get a clean view in the Blue Ocean UI. This is what I deducted from your question, and I don't know if this is something you are trying to achieve. Jenkins pipeline works most efficiently when it does its job in the context of a single application/project. Your task (provision 1,000 application, whatever it means) sounds like not the best fit for the single Jenkins pipeline job, and thus you may have to apply some workarounds.
Thanks for your kind words, Omar! I use IntelliJ IDEA in this video (and in my daily work.) You can download IDEA Community Edition for free with a support for Groovy language. Hope it helps. Take care and have a good day!
Really helpful video. I am still working on Jenkins pipelines and I am still a total noob with Groovy. I guess I can refactor a lot of code, to make it easier to read. By the way, do you also use IntelliJ for when you work on a Jenkins file? If yes, do use IntelliJ out of the box or do you use some plugin or something like that? Fancy intro :D
Hi, Sebastian! I use plain IntelliJ IDEA with no additional plugins for the Jenkinsfile support. There is a way to get some code autocompletion in the Jenkinsfile using the GDSL file. This blog post explains how to set it up - st-g.de/2016/08/jenkins-pipeline-autocompletion-in-intellij
@@szymonstepniak I used GDSL last year, when I had my first steps with Jenkins, but I was not completely happy with it. Maybe I should give it a try again. How much Groovy do you use in your Jenkinsfile. Just very simple stuff or even more adanced stuff. We have a pretty complex Maven Dependcy situation and depending on the branch, our artifacts needs to get the correct version from a different pom.xml. So in release/x we need pom.xml from another release/x (I mean nexus), same for feature, develop. Anyways, in the end, depending on what is going on (build from branch release/x, feature/x, develop or pull request from a to b) I need to get a slightly different version from Nexus. It works fine but with a lot of "ifs" and when conditions for different stages, it is a lot of code and maybe not the most simple way. Sorry for the confusing text. My question is, do you face similar problems and if yes, do you prefer to have more sepperat stages or how do you handle complex stuff. Hopefully you can understand, what I am talking about :D
@@SebastianPlagge I can't think of any example like that in any of the pipelines I worked with, but this is because I always advocate for the simplest pipeline workflow possible. For instance, in the company I currently work for, we treat every artifact as production-ready, so it doesn't matter what branch you build it from, it always has to pass the same set of stages. What I could suggest to you, however, is to look at Maven Profiles, and maybe try to orchestrate your situation with the profiles. Then you could just define a map of profiles, where you map a profile name to a list of branch patterns (including regex) that should use specific maven profile, e.g. def profiles = [ prod: ["master", /release\/.*/], dev: ["develop", /PR-.*/] ] And then you could extract profile name with something like this - gist.github.com/wololock/94e1ca3579486fdc6468f1fc9646a8b4 I don't know if it answers your specific problem, but maybe it will help you to improve your current workflow.
@@szymonstepniak Maven profiles are a pretty good idea. Unfortunately the projects are pretty old and there are maaany projects :(. Probably we will start to develop new stuff (this or next year), there I can put my ideas into it. The stuff we currently work on started many years before I entered the company. There are a lot of workarounds, many different coding styles and so on. The stages are pretty similar, the tricky part is to get the correct maven parent pom. The stuff worked fine with hudson and svn because the team did not care about "branches" and so on. They basically had one branch, which is develop :D. I am the one who introduced that stuff, because I want to push CI/CD. I will take a look at maven profiles for sure. Thanks for the example on GitHub!
How to iterate two lists of strings in a closure such that two list will have similar elements wherein some elements are prefix with a letter in one list? Is there are way to use nested closure?
Hi Yash! Could you elaborate on your question please? Are you trying to iterate the first list, and then for each element iterate another list and use this element as a prefix to construct a new string? list1.collect { a -> list2.collect { b -> "${a}_${b}" } }.flatten() Or is it something else you are trying to achieve? Let me know and I will do my best to help you. Have a good day!
@@szymonstepniak hi Szymon, I have one list of elements and now a new list needs to be added which has mostly same elements but few elements in the same list will be prefixed with alphabet b. The goal is to iterate through both the list at the same time where first element of one list and second Element of list two should be considered and then simultaneously the others. 1:1 I wanted to pass those elements tot he jenkins job. I am iterating through one list but now need to add second list in the same logic. Thank you Regards, Yash
@@themastermindisyash Can you share some code you have written so far and could you point to the place where you are confused? Also, sharing an expected result on an exemplary input will help me understand better what you are trying to achieve.
Hi Szymon, Greetings! We have two lists A = [abc, 1cdf, df123, 2bfc, dfyu, ghre, 6gyh] B = [abc, b1cdf, df123, b2bfc, dfyu, ghre, b6gyh ] These are the two lists. I want to pass the values of both the lists 1:1 (first element of first list with first element of second list) I want to pass these elements to stages parallelly Regards, Yash
@@themastermindisyash What does the "pass these elements to stages parallelly" mean specifically? I don't know what you mean by that. Regarding constructing a list of pairs that represent values at the same index of two lists, you can use the transpose() method. You can call it on a list that stores two (or more) lists as its elements. Assuming that A and B are the lists from your example, here is an exemplary code: def C = [A,B].transpose() It creates a new list C that looks like this: [[abc, abc], [1cdf, b1cdf], [df123, df123], [2bfc, b2bfc], [dfyu, dfyu], [ghre, ghre], [6gyh, b6gyh]] Now, you can use it in an iteration and do something while iterating such a list. For instance, if you want to print what both values in a stored pair are, you can do something like this: [A,B].transpose().each { a,b -> println "a = ${a}, b = ${b}" } (As you can see, it assigns first value to the a variable, and the second one to the b variable inside the each closure.) It will print something like this: a = abc, b = abc a = 1cdf, b = b1cdf a = df123, b = df123 a = 2bfc, b = b2bfc a = dfyu, b = dfyu a = ghre, b = ghre a = 6gyh, b = b6gyh This example simply prints the value of both elements, but you can use it as a starting point to achieve what you expect. Hope it helps.
Yes, I would say your intuition is accurate. From the formal perspective, a lambda function is an anonymous function that may use only the defined parameters (e.g. f x = x + 2). A closure, however, allows to use variables/symbols that are defined outside the expression (e.g. f x = x + y + 2), but it requires that all used variables/symbols are defined in the surrounding environment. That way a closure become a context-dependent, because this exemplary "y" variable may store a different value depending on the context. (Which I shown by using myName variable with different values in the video.) But Groovy closure breaks this formal definition by allowing using symbols/variables that are not defined in the surrounding context, and this allows Groovy closures to use e.g. delegation mechanism. Just like in the example I shown in the video - we can define a closure that uses `add(1)` method, and this method is not known in the environment the closure is defined in. It becomes clear what `add(1)` means when we explicitly set the delegate to the list object. So the Groovy closure depends on the surrounding context (`myName` variable in the example), as well as it depends on the delegation mechanism (using `add(1)` plus `closure.delegate = list`). I hope it helps and does not confuse you even more :-)
which groovy are you using I got errors "Caught: groovy.lang.MissingMethodException: No signature of method: java.util.regex.Matcher.replaceAll() is applicable for argument types: (Main$_run_closure1) values: [Main$_run_closure1@64d2d351] Possible solutions: replaceAll(java.lang.String) "
What do you think about this type of video? Should we do another survey like that in the future? :)
Maybe next time something about testing applications written in groovy?
The video was amazing, also I would like to see some things like Spock tricks.
yep, I also using Groovy for testing. Some tips for using it with DB and maybe with API's
new to groovy, big help not only to groovy I'm not familiar with some of these concepts especially delegation, currying, composition and etc. Thanks!
@@KidJV Happy to hear that! Have a good day! :)
As a Java developer I have spent a lot of time trying to understand groovy's closers in order to learn Gradle. Thx you a lot for this and groovy DSL tutorials. It helped me so much!!
Thanks for your kind words, Evgenij! Take care, and have a good day!
best groovy lessons I have ever seen
Thank you so much for your kind words! Take care, and have a good day!
Great content, impressive production. Brilliant.
Thank you, Damir! 🙏
Great work, Szymon!
I didn't know the "closure >> closure" syntax, brilliant :-D
The video quality is amazing!
Thanks, Brian! 😀 I'm happy to hear you found this video useful! 👍
This is so fabulous, cleared a lot of my own doubts. Thanks for the video!
Thanks for your kind words! It proves this video was worth creating. Have a good day!
Another great video - thanks. I always learn something new. Keep up the good work.
Thank you, Mark! That's the best thing I could hear today! 🙏
Great work. Thank you
Thank you!
Great job Szymon!
Thank you so much.
Thank you too!
Thank you! Very clear tutorial🫶
Glad it was helpful!
really helpful Groovy tips!
Thanks, Viktor! I'm glad to hear you find it useful. Have a good day! 👍
Great video. Keep up the good work.
Thank you so much for your kind words, Fazli! Take care and have a good day!
Really Helpful
Glad to hear that! Take care, and have a good day!
finally! someone showed me how to do an elegant histogram of data in groovy
```
def hist = [:].withDefault({ 0 })
def data = [3, 2, 2, 1, 1, 2, 4, 1, 3, 1]
data.each { hist[it] += 1 }
println("data: $data")
println("hist: $hist")
```
output
```
data: [3, 2, 2, 1, 1, 2, 4, 1, 3, 1]
hist: [3:2, 2:3, 1:4, 4:1]
```
thank you. 😀
There is even a shorter way to build your expected output:
data.countBy { it }
I'm gonna record a video about interesting collection methods in Groovy soon. Thanks for helping me in crafting this idea. Have a good day!
very helpfull..make more vedios about gsp, mysql connection groovy applications
Great content. Thank you.
Glad it was helpful! Have a good day!
Amazing video! Liked and subbed :)
Thanks a lot for the kind words! I hope you find my other Groovy related videos useful as well :-) Have a good day!
Gracias por compartir el conocimiento
Gracias!
8:23 The spaceship operator () delegates to the compareTo method
assert (1 1) == 0
assert (1 2) == -1
assert (2 1) == 1
assert ('a' 'z') == -1
Thanks for sharing these examples! Have a good day!
Hello @Szymon Stepniak, how can we pass specific elements to the closure say for example we want to pass 20 elements at a time from the map and pass it to the closure. We want to have sleep/wait after every 20 elements passed. The map has around 1000 elements.
Hi, Yash! Thanks for the comment! Could you share some code snippet or something, so I can better understand what you are trying to achieve? Are you trying to chunk a map (or a list), iterate it, and call some closure with arguments? Explaining your use case with an example would be most efficient to understand your problem better. I will do my best to help you.
@@szymonstepniak following is the line of code:
Script {
def appnamelist = params.APP_NAME.tokenize(',')
def parts = [:]
appnamelist.each { name ->
parts[name] = {
stage(){}
stage() {}
stage() {}
}
}
}
parallel parts
}
In the parts[name] I am getting 1000 elements I want to control the flow of elements such that only 50 elements are passed to parts[name]
Could you let me know what can be done for it.
@@themastermindisyash I don't know what exactly do you expect from this pipeline part, but it looks broken to me. If you put multiple stages inside the single parallel stage, Blue Ocean UI won't be even able to render them correctly and you will end up seeing only the first (out of three) stage from each part[name] parallel stage.
I tried to recreate your example with chunked input (using Groovy's Collection.collate(num) method that chunks a list to a list of lists of size num) to slice an input list of 100 elements into a list of lists of size 10, so I can create the "parts" map ten times with 10 elements only (a closure with 3 stages that just echo the name of the app), and then I can run "parallel parts" so it triggers 10 parallel stages (that contain 3 sequential stages), and it killed my Jenkins. I run this experiment only to show you that this is not the right way to utilize parallel stages in the Jenkins pipeline. Here is the code I run on my Jenkins, I also attached the screenshot of Blue Ocean UI that couldn't render those sequential stages in each parallel stage:
gist.github.com/wololock/acce19567af677a535337a7408d2e6ac
I would suggest taking a step back and rethinking what you are trying to achieve. This input of 1,000 app names sounds suspicious and it suggests that you are trying to utilize a single Jenkinsfile to build 1,000 apps in parallel, where every parallel stage triggers three sequential stages. This is wrong, this is going to kill your Jenkins server.
@@szymonstepniak Then which other approach can we adapt inorder to achieve the objective. I have to provision 1000 application with same set of stages for each one. Can you suggest a better approach here which can help achieve the objective?
@@themastermindisyash Well, the truth is I don't know what your objective is. I can only guess based on your question, which does not provide much information. I don't know what provisioning application means, and why you can't do it in the dedicated Jenkinsfiles. However, what I'm guessing is you want to parallel common steps and you want to split huge input between those parallel steps. The only thing that comes to my mind is something like this:
gist.github.com/wololock/3aeb2bdb48bf4ee1f57aada58ee8f6d0
It requires setting two Jenkins pipelines job. The first one (called Jenkinsfile-A.groovy in the attached gist) takes the input (1,000 app names) and it chunks this input to the desired size (I used chunk size 20 in the attached example). Then it iterates those chunks and for each chunk, it triggers the second pipeline job (named "parallel-job-name", you need to use a real pipeline job name instead) with the parameter APP_NAME and a value that represents only this chunk. It will trigger the job with the next chunk only when the previous job is done (you can change it by setting "wait" to false.) The second Jenkins pipeline (called Jenkinsfile-B.groovy in the attached gist) takes the chunked input and it runs those 3 stages (or any number of stages) and it parallelizes each stage for every value taken from the APP_NAME parameter. As you can see, it uses a "parallel" step in every stage, and this is on purpose - this way you can get a clean view in the Blue Ocean UI.
This is what I deducted from your question, and I don't know if this is something you are trying to achieve. Jenkins pipeline works most efficiently when it does its job in the context of a single application/project. Your task (provision 1,000 application, whatever it means) sounds like not the best fit for the single Jenkins pipeline job, and thus you may have to apply some workarounds.
Great content! What is the IDE/script editor you are using?
Thanks for your kind words, Omar! I use IntelliJ IDEA in this video (and in my daily work.) You can download IDEA Community Edition for free with a support for Groovy language. Hope it helps. Take care and have a good day!
Thanks for the amazing content.
Thank you, you're very kind.
Thank you
Really helpful video. I am still working on Jenkins pipelines and I am still a total noob with Groovy. I guess I can refactor a lot of code, to make it easier to read.
By the way, do you also use IntelliJ for when you work on a Jenkins file? If yes, do use IntelliJ out of the box or do you use some plugin or something like that?
Fancy intro :D
Hi, Sebastian! I use plain IntelliJ IDEA with no additional plugins for the Jenkinsfile support. There is a way to get some code autocompletion in the Jenkinsfile using the GDSL file. This blog post explains how to set it up - st-g.de/2016/08/jenkins-pipeline-autocompletion-in-intellij
@@szymonstepniak I used GDSL last year, when I had my first steps with Jenkins, but I was not completely happy with it. Maybe I should give it a try again.
How much Groovy do you use in your Jenkinsfile. Just very simple stuff or even more adanced stuff.
We have a pretty complex Maven Dependcy situation and depending on the branch, our artifacts needs to get the correct version from a different pom.xml.
So in release/x we need pom.xml from another release/x (I mean nexus), same for feature, develop.
Anyways, in the end, depending on what is going on (build from branch release/x, feature/x, develop or pull request from a to b) I need to get a slightly different version from Nexus. It works fine but with a lot of "ifs" and when conditions for different stages, it is a lot of code and maybe not the most simple way.
Sorry for the confusing text. My question is, do you face similar problems and if yes, do you prefer to have more sepperat stages or how do you handle complex stuff.
Hopefully you can understand, what I am talking about :D
@@SebastianPlagge I can't think of any example like that in any of the pipelines I worked with, but this is because I always advocate for the simplest pipeline workflow possible. For instance, in the company I currently work for, we treat every artifact as production-ready, so it doesn't matter what branch you build it from, it always has to pass the same set of stages. What I could suggest to you, however, is to look at Maven Profiles, and maybe try to orchestrate your situation with the profiles. Then you could just define a map of profiles, where you map a profile name to a list of branch patterns (including regex) that should use specific maven profile, e.g.
def profiles = [
prod: ["master", /release\/.*/],
dev: ["develop", /PR-.*/]
]
And then you could extract profile name with something like this - gist.github.com/wololock/94e1ca3579486fdc6468f1fc9646a8b4
I don't know if it answers your specific problem, but maybe it will help you to improve your current workflow.
@@szymonstepniak Maven profiles are a pretty good idea. Unfortunately the projects are pretty old and there are maaany projects :(.
Probably we will start to develop new stuff (this or next year), there I can put my ideas into it. The stuff we currently work on started many years before I entered the company.
There are a lot of workarounds, many different coding styles and so on.
The stages are pretty similar, the tricky part is to get the correct maven parent pom.
The stuff worked fine with hudson and svn because the team did not care about "branches" and so on. They basically had one branch, which is develop :D. I am the one who introduced that stuff, because I want to push CI/CD.
I will take a look at maven profiles for sure. Thanks for the example on GitHub!
Sounds good, good luck, Sebastian! 👍
How to iterate two lists of strings in a closure such that two list will have similar elements wherein some elements are prefix with a letter in one list? Is there are way to use nested closure?
Hi Yash! Could you elaborate on your question please? Are you trying to iterate the first list, and then for each element iterate another list and use this element as a prefix to construct a new string?
list1.collect { a -> list2.collect { b -> "${a}_${b}" } }.flatten()
Or is it something else you are trying to achieve? Let me know and I will do my best to help you. Have a good day!
@@szymonstepniak hi Szymon, I have one list of elements and now a new list needs to be added which has mostly same elements but few elements in the same list will be prefixed with alphabet b. The goal is to iterate through both the list at the same time where first element of one list and second Element of list two should be considered and then simultaneously the others. 1:1
I wanted to pass those elements tot he jenkins job. I am iterating through one list but now need to add second list in the same logic.
Thank you
Regards,
Yash
@@themastermindisyash Can you share some code you have written so far and could you point to the place where you are confused? Also, sharing an expected result on an exemplary input will help me understand better what you are trying to achieve.
Hi Szymon, Greetings!
We have two lists
A = [abc, 1cdf, df123, 2bfc, dfyu, ghre, 6gyh]
B = [abc, b1cdf, df123, b2bfc, dfyu, ghre, b6gyh ]
These are the two lists. I want to pass the values of both the lists 1:1 (first element of first list with first element of second list)
I want to pass these elements to stages parallelly
Regards,
Yash
@@themastermindisyash What does the "pass these elements to stages parallelly" mean specifically? I don't know what you mean by that.
Regarding constructing a list of pairs that represent values at the same index of two lists, you can use the transpose() method. You can call it on a list that stores two (or more) lists as its elements. Assuming that A and B are the lists from your example, here is an exemplary code:
def C = [A,B].transpose()
It creates a new list C that looks like this:
[[abc, abc], [1cdf, b1cdf], [df123, df123], [2bfc, b2bfc], [dfyu, dfyu], [ghre, ghre], [6gyh, b6gyh]]
Now, you can use it in an iteration and do something while iterating such a list. For instance, if you want to print what both values in a stored pair are, you can do something like this:
[A,B].transpose().each { a,b -> println "a = ${a}, b = ${b}" }
(As you can see, it assigns first value to the a variable, and the second one to the b variable inside the each closure.) It will print something like this:
a = abc, b = abc
a = 1cdf, b = b1cdf
a = df123, b = df123
a = 2bfc, b = b2bfc
a = dfyu, b = dfyu
a = ghre, b = ghre
a = 6gyh, b = b6gyh
This example simply prints the value of both elements, but you can use it as a starting point to achieve what you expect. Hope it helps.
Thank you, I have always found the purpose of a closure a bit confusing! Am I correct to say it's sort of like a lambda function?
Yes, I would say your intuition is accurate. From the formal perspective, a lambda function is an anonymous function that may use only the defined parameters (e.g. f x = x + 2). A closure, however, allows to use variables/symbols that are defined outside the expression (e.g. f x = x + y + 2), but it requires that all used variables/symbols are defined in the surrounding environment. That way a closure become a context-dependent, because this exemplary "y" variable may store a different value depending on the context. (Which I shown by using myName variable with different values in the video.) But Groovy closure breaks this formal definition by allowing using symbols/variables that are not defined in the surrounding context, and this allows Groovy closures to use e.g. delegation mechanism. Just like in the example I shown in the video - we can define a closure that uses `add(1)` method, and this method is not known in the environment the closure is defined in. It becomes clear what `add(1)` means when we explicitly set the delegate to the list object. So the Groovy closure depends on the surrounding context (`myName` variable in the example), as well as it depends on the delegation mechanism (using `add(1)` plus `closure.delegate = list`). I hope it helps and does not confuse you even more :-)
@@szymonstepniak It helps a ton! Thank you so much for sharing your knowledge.
Nice video, but please remove the background music, its really very distracting.
Thanks for your kind words and suggestion, Lakshmi! Take care, and have a good day!
Any groovy developer DM me I have a problem in coding my project is total in groovy technology I am new to groovy
שמעון
which groovy are you using I got errors
"Caught: groovy.lang.MissingMethodException: No signature of method: java.util.regex.Matcher.replaceAll() is applicable for argument types: (Main$_run_closure1) values: [Main$_run_closure1@64d2d351]
Possible solutions: replaceAll(java.lang.String)
"
I think it was Groovy 3 at the time of recording this video.