Database Values Plugin upgraded for JIRA 6

I just finished upgrading the JIRA Database Values Plugin for JIRA 6. Download it from https://marketplace.atlassian.com/plugins/org.deblauwe.jira.plugin.database-values-plugin

Posted in Uncategorized | Tagged , | Leave a comment

Upgrading your plugin to JIRA 5.1 presentation slides

Last week, I gave a presentation on upgrading the JIRA Database values plugin to JIRA 5.1 for the Atlassian Belgian User Group. For those that are interested, the slides can be viewed here: https://docs.google.com/presentation/d/100KHFe-oxlVmud6nhSbgdjUaXFO2Jnf7qPcDhZpPGDo/pub?start=false&loop=false&delayms=3000

Posted in Programming | Tagged | Leave a comment

JIRA Database Values Plugin for JIRA 5.1 released!

I just released the new version of the JIRA Database Values Plugin. It is now compatible with JIRA 5.1 and 5.2. Most notable change is that under the hood jQuery is used now instead of Scriptaculous.

See Atlassian Marketplace for the download.

The source code has also been migrated to the excellet Bitbucket service. Sources can be found at https://bitbucket.org/wimdeblauwe/jdvp and the documentation at the embedded wiki: https://bitbucket.org/wimdeblauwe/jdvp/wiki/Home

This version would not have been possible without the people that donated on GoFundMe so a big thank you to them all!

Enjoy it!

Wim

PS: One breaking change is that linking of multiple custom fields is no longer supported. It never worked really well anyway and I had to remove it to support the inline editing capabilities of JIRA 5.1.

Posted in Uncategorized | Tagged , | Leave a comment

Tooltips in detached windows with Adobe AIR

I just had an issue where we display a tooltip on a custom component in Flex. This worked fine until we also used this component in a detached window in Adobe AIR. We never saw the tooltip in the detached window.

This is the code we used before in the custom component:

m_tooltip = ToolTipManager.createToolTip( tooltip, event.stageX + 10, event.stageY + 10 );

This is what fixed it:

m_tooltip = ToolTipManager.createToolTip( tooltip, event.stageX + 10, event.stageY + 10, null, this );

The important part is the this parameter passed into the createToolTip method. If not passed into the method, the framework will always use the FlexGlobals.topLevelApplication, which is not your detached window. By passing in the reference to custom component to the tooltip manager, it will use the correct native window to display the tooltip in.

Posted in Programming | Tagged | Leave a comment

Database Values Plugin for JIRA 5.1

I have been quite busy with upgrading the JIRA Database Values Plugin to support JIRA 5.1. I never thought I would pull it off with the inline editing stuff, but it is looking good so far. If you are interested, please consider donating on http://www.gofundme.com/1efxt4 . As soon as I reach the fund goal, I will release the new version. If you donate 50 euro or more, you can get early access to test versions.

Posted in Programming | Tagged , | Leave a comment

Restarting an Adobe AIR application when using video

An Adobe AIR application can restart itself, using the code found in this StackOverflow question. However, it does not seem to work always in our application. We are creating new NativeWindows to allow the user to detach a window from the main application window to support using multiple monitors. The problem seemed to be more frequent when windows were detached.
After more investigation, it seemed that not the detached windows, but the video playing the detached windows was the problem. We now first make sure we stop all video that is playing before we do the restart code. We are using the Video class in our application, so we stop it like this:

video.attachNetStream( null );
video.clear();

This way, the restarts of our application have become reliable.

Posted in Programming | Tagged , , | 1 Comment

Updating JIRA links in Confluence when migrating to new server

We just finished the process of migrating our internal JIRA server running on Windows to a new Ubuntu machine. The migration process went fairly smooth. However, the URL of JIRA has also changed and we have a lot of links in our Confluence wiki pointing to JIRA.

To update all the links manually, would be tedious and error-prone, so I used some Groovy together with the excellent Confluence Command Line Interface to make this automatic.

This is the main loop of my program, no surprises there:

    public static void main(String[] args)
    {
        logger.info("Getting configuration");
        ConfigurationLoader loader = new ConfigurationLoader();
        Configuration configuration = loader.getConfiguration();

        List<String> spaces = getAllSpaces(configuration);
        for (String spaceName: spaces)
        {
            logger.debug("Updating space {}", spaceName)

            List<String> pageNames = getAllPages(configuration, spaceName);
            for (String pageName: pageNames)
            {
                logger.debug("  Updating page {}", pageName)
                updateJiraUrl(configuration, spaceName, pageName);
            }
        }
    }

The ConfigurationLoader and the Configuration objects are just keeping track of what the URL of confluence is, and the path to the Confluence CLI. So, the actual code first gets all the spaces, then all the pages into that space and then we try to update any old JIRA URL we find on that page.

The function to get all the spaces call the Confluence CLI and then does some string magic on the result to just get the keys of the spaces only:

    static List<String> getAllSpaces(LiquidPlannerConfiguration configuration)
    {
        File tempFile = File.createTempFile("confluence-spaces-list", "txt")
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "getSpaceList",
                               "--file",
                               tempFile.getAbsolutePath())

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)

        List<String> result = new ArrayList<String>();
        tempFile.eachLine {
            def spaceKey = it.find(/^"[A-Z0-9]*"/);
            if (spaceKey)
            {
                result.add(spaceKey.substring(1, spaceKey.length() - 1))
            }
        }

        tempFile.delete();
        return result;
    }

For each space key, we get a list of all the page names:

    static List<String> getAllPages(LiquidPlannerConfiguration configuration, String spaceKey)
    {
        File tempFile = File.createTempFile("confluence-pages-list", "txt")
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "getPageList",
                               "--space",
                               spaceKey,
                               "--file",
                               tempFile.getAbsolutePath())

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)

        List<String> result = new ArrayList<String>();
        tempFile.eachLine {
            result.add(it);
        }

        tempFile.delete();
        return result;
    }

For update of the page, we first need to get the content of the page and then update the page:

    static void updateJiraUrl(LiquidPlannerConfiguration configuration, String spaceName, String pageName)
    {
        File pageContents = null;
        try
        {
            pageContents = getPageContent(configuration, spaceName, pageName);
            updatePageContents(configuration, spaceName, pageName, pageContents);
        }
        finally
        {
            if( pageContents )
            {
                pageContents.delete()
            }
        }

    }

Getting the content of the page is just calling Confluence CLI:

    static File getPageContent(LiquidPlannerConfiguration configuration, String spaceName, String pageName)
    {
        File tempFile = File.createTempFile(spaceName + "-page", "txt")
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "getSource",
                               "--space",
                               spaceName,
                               "--title",
                               pageName,
                               "--file",
                               tempFile.absolutePath)

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)

        return tempFile;
    }

Updating the page is done here:

    static void updatePageContents(LiquidPlannerConfiguration configuration, String spaceName, String pageName, File pageContents)
    {
        if( !pageContents.text.contains( "companyweb.company.com:8888/jira") )
        {
            return;
        }

        File replacedFile = File.createTempFile(spaceName + "-page-replaced", "txt");

        def replacedFileWriter = new FileWriter( replacedFile );
        new FileReader( pageContents ).transformLine( replacedFileWriter ) {
            it.replaceAll("companyweb\\.company\\.com:8888/jira", "jira\\.company\\.com:8888")
        }
        
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "storePage",
                               "--space",
                               spaceName,
                               "--title",
                               pageName,
                               "--file",
                               replacedFile.absolutePath)

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)
    }

There are 2 pieces of code in this last function that I would like to highlight:

if( !pageContents.text.contains( "companyweb.company.com:8888/jira") )
{
   return;
}

This part just reads the text of the confluence page which we saved to a file and checks if the old URL is present. If it is not present, we just return from the method and thus do not change anything.
Notice how easy it is in Groovy to get the content of a file as a String. The File#getText() method is something that is part of the Groovy JDK. See http://groovy.codehaus.org/groovy-jdk/java/io/File.html for more interesting methods on File added by Groovy.

The 2nd piece of code does the actual replacement, again with a very nice piece of Groovy code:

File replacedFile = File.createTempFile(spaceName + "-page-replaced", "txt");

def replacedFileWriter = new FileWriter( replacedFile );
new FileReader( pageContents ).transformLine( replacedFileWriter ) {
    it.replaceAll("companyweb\\.company\\.com:8888/jira", "jira\\.company\\.com:8888")
}

What we have here is reading from the pageContents file and writing it out to the replacedFile. Just before the write of each line, the closure is called so we can do some transformation on that line. Here, we use the replaceAll method that takes a regular expression to do the URL matching and replacing. Since a dot (.) is a special character, we have to escape it with a backslash (\) and since a backslash is also a special character, we also have to escape that one.

That is all there is to it. I used Confluence CLI 2.4.0 which is the last one at the time of writing and Confluence 3.4 which is the version we have currently at our company.

PS: If you want to run this yourself, you just need 1 more function that reads the output of the Confluence CLI process:

    static void writeProcessOutput(Process process) throws Exception
    {
        InputStreamReader tempReader = new InputStreamReader(
                new BufferedInputStream(process.getInputStream()));
        BufferedReader reader = new BufferedReader(tempReader);
        while (true)
        {
            String line = reader.readLine();
            if (line == null)
                break;
            System.out.println(line);
        }
    }
Posted in Programming | Tagged , | 2 Comments