GGTS: Clean up Grails 2.0 output

GGTS logoHave you ever had in Groovy/Grails Tool Suite (GGTS) that console output, by a running Grails application, which is exactly the same as the previous output, just isn’t displayed?

This can often be seen with println statements for debug-purposes e.g. in a Controller, which you think should output some line to the console every time, but simply doesn’t.

class TestController {
	def index() { 
		println "index called"
	}
}

When http://localhost:8080/test/test/index is invoked repeatedly in the browser, you just keep seeing only the first occurence.

....index called

When the same message repeatedly is sent to the console a certain convenience feature of GGTS swallows some output – if it looks the same. It has to do with the – since Grails 2.0 introduced – ANSI codes to make some output to the console coloured or re-appear on the same line.

Kris de Volder gives a nice example in JIRA issue STS-3499 about how multiple lines such as

Resolving Dependencies.
Resolving Dependencies..
Resolving Dependencies...
Resolving Dependencies....

are supposed to ‘rewrite over themselves’ on ANSI-supported consoles, so you’d only see

Resolving Dependencies...<increasing periods>

on the same line.

Output in the GGTS non-ANSI-enabled console is stripped from these codes – which would result in additional output which some people find unpleasant. So GGTS uses a workaround – which is enabled by default – and strips the beginning of the output which matches previous output and only print the remainder.

So if you were wondering why

class BootStrap {
	def init = { servletContext ->
		['A', 'B', 'B'].each { println it }
	}
}

would only print

|Running Grails application
A
B
|Server running. Browse to http://localhost:8080/test

instead of

|Running Grails application
A
B
B
|Server running. Browse to http://localhost:8080/test

you know now this is not a bug :-)

You have to disable the option ‘Clean Grails 2.0 output‘ in the GGTS preferences under Groovy > Grails > Grails Launch to prevent this swallowing-behaviour.

Now your output appears in GGTS when you want it to appear :-)

Starting a video blog: Grails in the First8Friday series

I recently recorded the first Grails video in my company’s newest video-blog: the First8Friday-series.

A videoblog?

We already wrote “regular” blog articles, but sometimes you’ve got to try different things as a company, so why not a video blog — for now in Dutch only. Since we know Java, the first few episodes are planned to be about Grails, one of the more powerful, full-stack web application frameworks I’d love to use.

This opening episode shows how easy it is to create a CRUD application for managing spaceships using the scaffolding ability of the framework. It’s pretty low-level to begin with, so Grails-beginners can see what Grails can do for them.

Creating a video blog

For the first time we didn’t want to set the bar too high too handle. Even though it’s only a few minutes long, it cost quite some time to produce! Getting the content as if one was writing a regular blog post with code snippets and images was the easy part :-)

The bulk of the time was taken by a few things.

The script!

It’s not just like a blog post, it’s like a movie. You have to have script or a story-board. Going from just “content” to a story to tell is quite different: figuring out how to start, what’s in the middle and how to end. When should I be talking to the viewer on screen and when would a voice-over suffice?

Some parts were still images or screen casts I had to record before or afterwards on my laptop, where I not only had to deal with recording the actual action (command-line operations, viewing/editing of some code in the editor), but also revising the story itself as I was making image snapshots and screen captures. Why doesn’t have GGTS (or Eclipse) a presentation-mode, is a regular text-editor with syntax-highlighting maybe better? Why introducing all kinds of fluff in examples when it’s not about thát fluff.

ScriptImage: indiegogo.com

The green screen

The hardware, such as lights, camera and microphone, was easily acquired, but then using it is another story.

The lighting was initially flawed, unwanted shadows, distance between myself and the camera vs the microphone, positioning of myself (somewhat right from centre, visible from the waist up) etc.

The Green Screen at First8Image: The Green Screen at First8

The spoken lines of text

Re-runs of the same introduction and paragraphs with different pronunciations, rhythm, etc. What seems like a well-crafted sentence on paper, is a real tongue twister on camera :-)

It also doesn’t look great if a sentence takes 1 minute to complete, because the version on paper reads nice and poetic like that, but you can’t memorise it completely without an auto-cue. So yes, long lines without an auto-cue is hard (for any non-actor, like me), and the video-editor-person can’t keep trying to fade me out and in real quickly whenever I had to look sideways to my speaker notes.

The first-time technicalities

Seems that my screencasts were recorded by default as .avi files encodes as H264 and MP3 audio. Reviewing my video snippets with the media player caused no worries, but somehow integrating these in Adobe Premiere in the real production – you know, in the green part of the green screen – caused either a black edge, or flickering pixels, or, or… Since we couldn’t figure out all the causes (or even more, solutions), even after playing with some interlace-settings, I re-recorded screencasts as MP4 which could be handled more easily.

Better figured this out before recording anything at all :-)

Suffice to say, producing the real thing and integrating a kinds of screenshots, screencasts and other Powerpoint-like slides I could think of, made my video-producer also need to Google for a quite a few quirks and “features” of the his video-editing software himself.

All kinds of videos

Even though it seems we’ve encountered nothing but hardship by the looks of it, it’s been quite a fun experience and a lot of these things can be categorised as first-time start-up issues. While we’re busy now with video’s 2 and beyond, we’ve already gathered some valuable insights of things to do differently.

If you’re Dutch you can actually understand what’s going on, so if you haven’t done so, head over to First8Friday edition 1 about Grails on the company blog.

I welcome any comments or feedback or just any thoughts on video blogs (or regular screencasts) you might have seen, create or use in your daily life.

Happy 2015

Dear reader of Ted Vinke’s Blog,

I wish you all the best for 2015 and may you, next to new, fun and inspiring software projects, spend lots of wonderful time with friends and family!

Kind regards,
Ted Vinke

Happy New Year 2015

Release Notes with Groovy and JIRA REST API: Part II – Separate responsibilities

A while ago I described how to get release notes from JIRA with Groovy, so I thought it’s time to do a small follow up which is long overdue :-).

Let’s split things up to manageable classes with their own responsibility:

  • a JIRA REST client which encapsulates connecting to my JIRA instance, such as (HTTP) getting or posting from and to an url while using the proper credentialsgroovy-logo-medium
  • a JIRA query builder, responsible for building JQL snippets to be used for the JIRA search REST API

We’ll start at the bottom, so we’ll build upon the classes we already have.

Just Being Typesafe

Although a lot of the date we’re getting back from JIRA can be used as-is in Groovy, I decided to make the status of a JIRA issue, e.g. “Open”, into a trivial enum.

enum JiraStatus {
    OPEN("Open"), IN_PROGRESS("In Progress"), RESOLVED("Resolved"), REOPENED("Reopened"), CLOSED("Closed");

    JiraStatus(String value) { this.value = value }
    private final String value
    String value() { return value }
}

I’ll stick to Strings in the remainder of this post :-)

Building a JQL Query

Queries you can execute within JIRA itself in the Issue Navigator can also be performed through the REST API. Because there are several elements which constitute a JQL-query I decided to use the builder pattern to programmatically create queries through the JiraQueryBuilder.

import groovyx.net.http.RESTClient
import JiraStatus

/**
 * Builder for a JQL-snippet, used for the JIRA search REST API.
 */
class JiraQueryBuilder  {

    private String project
    private int[] sprintIds
    private String sprintFunction
    private String[] issueKeys
    private String[] issueTypes
    private String[] excludedIssueTypes
    private String[] resolution
    private JiraStatus[] status
    private String[] labels
    private String fixVersion
    private String orderBy

    /**
     * Create a new query builder, querying by default everything for specified project, ordered by key.
     */
    public JiraQueryBuilder(String project) {
        assert project
        this.project = project
        withIssueTypes("standardIssueTypes()")
        withOrderBy("key")
    }

    JiraQueryBuilder withSprintIds(int...sprintIds) {
        this.sprintIds = sprintIds
        return this
    }

    JiraQueryBuilder withIssueKeys(String...issueKeys) {
        this.issueKeys = issueKeys
        return this
    }

    JiraQueryBuilder withIssueTypes(String... issueTypes) {
        this.issueTypes = issueTypes
        return this
    }

    JiraQueryBuilder withExcludedIssueTypes(String... excludedIssueTypes) {
        this.excludedIssueTypes = excludedIssueTypes
        return this
    }

    JiraQueryBuilder withResolution(String... resolution) {
        this.resolution = resolution
        return this
    }

    JiraQueryBuilder withStatus(JiraStatus... status) {
        this.status = status
        return this
    }

    JiraQueryBuilder withLabels(String... labels) {
        this.labels = labels
        return this
    }

    JiraQueryBuilder withFixVersion(String fixVersion) {
        this.fixVersion = fixVersion
        return this
    }

    JiraQueryBuilder withOrderBy(String orderBy) {
        this.orderBy = orderBy
        return this
    }

    String build() {
        String jql = "project = ${project}"

        if (sprintIds) {
            jql += " AND sprint in (${sprintIds.join(',')})"
        }

        if (sprintFunction) {
            jql += " AND sprint in ${sprintFunction}"
        }

        if (issueKeys) {
            jql += " AND issuekey in (${issueKeys.join(',')})"
        }

        if (issueTypes) {
            jql += " AND issuetype in (${issueTypes.join(',')})"
        }

        if (excludedIssueTypes) {
            jql += " AND issuetype not in (${excludedIssueTypes.join(',')})"
        }

        if (status) {
            // turn (IN_PROGRESS, RESOLVED) into (Resolved, In Progress)
            jql += " AND status in (${status*.value().join(',')})"
        }

        if (resolution) {
            jql += " AND resolution in (${resolution.join(',')})"
        }

        if (labels) {
            jql += " AND labels in (${labels.join(',')})"
        }

        if (fixVersion) {
            jql += " AND fixVersion = \"${fixVersion}\""
        }

        if (orderBy) {
            jql += " order by ${orderBy}"
        }

        return jql
    }
}

Connecting to JIRA

Finally I needed to encapsulate the connection to a JIRA instance, allowing me to (HTTP) GET and POST from and to it, including using the proper credentials.

// require(groupId:'org.codehaus.groovy.modules.http-builder', artifactId:'http-builder', version:'0.5.2')
import net.sf.json.groovy.*
import groovyx.net.http.RESTClient
import static JiraStatus.*

/**
 * JIRA REST client wrapper around groovyx.net.http.RESTClient
 */
class JiraRESTClient extends groovyx.net.http.RESTClient {

    private static final String DEFAULT_PROJECT = "XXX"
    private static final String DEFAULT_SEARCH_URL = "http://jira/rest/api/latest/"

    String username
    String password
    def credentials = [:]

    /**
     * Create a REST client using Maven properties jira.username and jira.password for JIRA username and password.
     */
    static JiraRESTClient create(def project) {

        String jiraUsername = project.properties['jira.username']
        String jiraPassword = project.properties['jira.password']

        if (!jiraUsername?.trim()) {
            throw new IllegalArgumentException("Empty property: jira.username")
        }

        if (!jiraPassword?.trim()) {
             throw new IllegalArgumentException("Empty property: jira.password")
        }

        return create(jiraUsername, jiraPassword)
    }

    /**
     * Create a REST client using provided JIRA username and password.
     */
    static JiraRESTClient create(String username, String password) {
        return new JiraRESTClient(DEFAULT_SEARCH_URL, username, password)
    }

    private JiraRESTClient(String url, String username, String password) {
        super(url)
        assert username
        assert password

        log.debug "Created for user=${username}, url=" + url
        this.username = username;
        this.password = password;

        credentials['os_username'] = this.username
        credentials['os_password'] = this.password
    }

    def get(String path, def query) {

        try {
            def response = get(path: path, contentType: "application/json", query: query)
            assert response.status == 200
            assert (response.data instanceof net.sf.json.JSON)
            return response
        } catch (groovyx.net.http.HttpResponseException e) {
            if (e.response.status == 400) {
                // HTTP 400: Bad Request, JIRA returned error
                throw new IllegalArgumentException("JIRA query failed, response data=${e.response.data}", e)
            } else {
                throw new IOException("JIRA connection failed, got HTTP status ${e.response.status}, response data=${e.response.data}", e)
            }

        }
    }

    def post(String path, def body, def query) {

        try {
            def response = post(path: path, contentType: "application/json", body: body, query: query)
            return response
        } catch (groovyx.net.http.HttpResponseException e) {
            if (e.response.status == 400) {
                // HTTP 400: Bad Request, JIRA returned error
                throw new IllegalArgumentException("JIRA query failed, got HTTP status 400, response data=${e.response.data}", e)
            } else {
                throw new IOException("JIRA connection failed, got HTTP status ${e.response.status}, response data=${e.response.data}", e)
            }

        }
    }

    /**
     * Search JIRA with provided JQL e.g. "project = XXX AND ..."
     * @returns response
     * @throws IllegalArgumentException in case of bad JQL query
     * @throws IOException in case of JIRA connection failure
     * @see JiraQueryBuilder to create these JQL queries
     */
    def search(String jql) {
        assert jql

        def query = [:]
        query << credentials
        query['jql'] = jql

        query['startAt'] = 0
        query['maxResults'] = 1000

        log.debug "Searching with JQL: " + jql
        return get("search", query)
    }

Credentials are appended in plain sight to the url, but for internal use I found this acceptable. The search() gets a JQL string passed, appends the credentials and delegates to our own get() which actually performs groovyx.net.http.RESTClient‘s get() to make it happen.

Querying Release Notes

The query to get all Resolved or Closed issues for a certain fix version  can be encapsulated in a getReleaseNotes method, such as seen below.

def getReleaseNotes(String fixVersion) {
  String jql = new JiraQueryBuilder(DEFAULT_PROJECT)
                    .withStatus(RESOLVED, CLOSED)
                    .withFixVersion(fixVersion)
                    .build()
  return search(jql)
}

Done

I’m still kickstarting this script with e.g. the gmaven-plugin which allows me to get my JIRA credentials from e.g. settings.xml, while the fix version for the JIRA query can be supplied as a property on the command-line.