Adaptavist Labs2018-10-10T10:35:57+01:00http://labs.adaptavist.comAdaptavist.comlabs@adaptavist.comA strange bug on AWS Lambda2018-10-09T00:00:00+01:00http://labs.adaptavist.com/code/2018/10/09/a-strange-bug-on-aws-lambda<p>At Adaptavist we run a lot of <a href="https://aws.amazon.com/lambda/">Lambda functions</a> to provide functionality in our <a href="https://marketplace.atlassian.com/vendors/81/adaptavist">Cloud apps</a>. We use both the Java and Node.js runtimes for lambdas, and recently we came across a weird bug in production that was caused by the way that the lambda function runtime is re-used in AWS.</p>
<p>As part of our <a href="https://marketplace.atlassian.com/apps/6820/scriptrunner-for-jira?hosting=cloud&tab=overview">ScriptRunner for Jira Cloud app</a>, we run a service (built using lambda functions) that processes some input data from our customer's Jira instances and then PUTs the output back into our customer's Jira instances. Part of the processing logic requires us to have a list of the fields that exist in our customer's Jira instances in memory during the processing. This lambda function is triggered, in parallel, once per user per Jira instance, for a subset of the users in each Jira instance.</p>
<p>Lets say we have 10 customers and 2 users per customer that need this service. We would trigger the lambda function that does the processing 20 times in parallel. The processing takes usually less than 30 seconds, but we run the process every minute. That frequency is important.</p>
<p>I mentioned that we need a list of fields in memory. When we first built the service we fetched that list on demand for each item in the input that the lambda receives. Recently we started caching that list of fields, so that we would only need to fetch it once per lambda invocation.</p>
<p>The lambda uses a node module that our frontend code for ScriptRunner uses and it was inside this module that we added the caching.</p>
<p>Once we deployed this change, everything seemed to be fine, until we got some support requests along the lines of "the service is telling me the fields are invalid". We checked the logs and sure enough there were lots of error reported about fields mentioned in the lambda input being invalid fields - ie not in the list of fields we should have been fetching from Jira.</p>
<p>I mentioned at the start of the blog post that we use Java and Node.js runtimes, and when developing our Java lambdas we had been very cognizant of the fact that a single lambda instance can have its runtime frozen once it completes and that a second request a short time later can trigger the runtime to be defrosted/thawed in order to process the second lambda request and avoid a cold start, therefore we should clean up after ourselves nicely in case the JVM is re-used.</p>
<p>For our Node.js lambdas we hadn't really thought that much about it, but once we saw this bug in production I had a hunch what might be happening.</p>
<p>Because we run the service so frequently (every minute) the lambda function is always "hot", which means the Node.js runtime is shared between invocations of the function. We had introduced caching into a node module and that module was not being re-initialized because the runtime (and therefore data stored inside modules) was being kept between invocations! Because we only have one lambda function for this process, the first time it ran it cached the field list from one Jira instance and all the subsequent invocations of the lambda that were supposed to be for other Jira instances just used the cached data which didn't match the input!</p>
<p>So, if you cache anything in a lambda function, make sure you think about the possibility of the cached value being shared between invocations of that lambda!</p>
How to run Karma tests in browsers in Docker2018-01-17T00:00:00+00:00http://labs.adaptavist.com/testing/2018/01/17/how-to-run-karma-tests-in-browsers-in-docker<p>There are a couple of benefits to running karma tests inside of browsers that are running inside of Docker:</p>
<ul>
<li>its easy to test against multiple versions of a browser</li>
<li>you can test against different browser versions than your build server has installed</li>
<li>you can test against browsers that aren't even installed on your build server!</li>
</ul>
<p>In my scenario I wanted to achieve two things:</p>
<ul>
<li>testing against a more recent version of Firefox than our build servers had installed</li>
<li>making the tests more reliable.</li>
</ul>
<p>Often our Karma tests would fail because Firefox didn't respond within 10s. By "dockerizing" Firefox I hoped to gain
some reliability.</p>
<h2 id="firstly-some-things-to-avoid">Firstly some things to avoid:</h2>
<h4 id="don-39-t-build-your-own-docker-containers">Don't build your own Docker containers</h4>
<p>You'll need to solve problems like how to install the various browsers latest releases on whatever base OS your container
use; you'll either need to maintain these containers or extend your automated builds to include generating the container
for each run, which could be quite slow.</p>
<p>In the end I used <a href="https://hub.docker.com/r/cypress/browsers/">cypress/browsers:chrome56-ff47</a> but I probably could have used the selenium images instead.</p>
<h4 id="don-39-t-move-your-entire-karma-build-preprocessing-to-run-inside-the-docker-container">Don't move your entire Karma build/preprocessing to run inside the Docker container</h4>
<p>We have what feels like a relatively large Webpack build pipeline and shifting that from running on the host machine to
running in the Docker container was a mistake:</p>
<ul>
<li>each container would have an empty yarn cache without some non-trivial config</li>
<li>introducing Docker adds several layers of additional software complexity to the procedure unnecessarily</li>
<li>the Docker container would run out of memory while bundling all our <del>dependencies</del> code.</li>
</ul>
<h2 id="the-success-story">The success story:</h2>
<p>All that Karma needs to do is launch a new Docker container pointing the relevant browser at the right URL.</p>
<blockquote>
<p>NB: for automated builds you'll want to have pulling the Docker image as a prerequisite to the Karma test run as it'll
take a while... Alternatively you can guess how long it will take and bump the connection timeouts in your Karma config.
We happen to run our tests via gradle so I added a task dependency on a new <a href="https://github.com/bmuschko/gradle-docker-plugin">Gradle Docker Plugin</a> task that pulls
the image.</p>
</blockquote>
<p>Thankfully Karma can launch a browser via a <a href="http://karma-runner.github.io/2.0/config/browsers.html">shell script</a>. Just install the <a href="https://www.npmjs.com/package/karma-script-launcher">karma-script-launcher</a> plugin and
provide an ABSOLUTE path to the script in your browsers array. At the time of writing no errors/warnings are provided by
Karma if it can't find the script.</p>
<p>That script <a href="https://groups.google.com/forum/#!topic/karma-users/uJa5YdpuRu8">should not terminate</a> once the browser has started.</p>
<p>The script receives a single argument that is the URL of the page Karma wants the browser to use. Because we're running
our browsers in Docker we need to modify the hostname.</p>
<p>Additionally we also need to <a href="https://github.com/angular/ci.angularjs.org/blob/5789e4f68e22ee7b8593981e3f806f2f38c9c6ab/bin/ie_.sh#L17">shutdown the Docker container</a> when we're done.</p>
<p>So here's what our Firefox shell script looks like:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash</span>
<span class="nb">set</span> -u
<span class="nb">set</span> -e
<span class="nb">set</span> -o pipefail
<span class="nv">URL</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
<span class="nv">OSNAME</span><span class="o">=</span><span class="k">$(</span>uname<span class="k">)</span>
<span class="c"># Fix the URL so the container can access the host</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$OSNAME</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"Darwin"</span> <span class="o">]]</span>; <span class="k">then
</span><span class="nv">DOCKER_URL</span><span class="o">=</span><span class="k">$(</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$URL</span><span class="s2">"</span> | sed s/localhost/docker.for.mac.localhost/g | sed s/0.0.0.0/docker.for.mac.localhost/g <span class="k">)</span>
<span class="k">else
</span><span class="nv">DOCKER_URL</span><span class="o">=</span><span class="k">$(</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$URL</span><span class="s2">"</span> | sed s/localhost/172.17.0.1/g | sed s/0.0.0.0/172.17.0.1/g <span class="k">)</span>
<span class="k">fi
</span><span class="nb">echo</span> <span class="s2">"Launching firefox: </span><span class="nv">$DOCKER_URL</span><span class="s2">"</span>
<span class="c"># Setup the shutdown/cleanup function</span>
killFirefox<span class="o">()</span> <span class="o">{</span>
docker rm -fv firefox-karma
<span class="o">}</span>
<span class="nb">trap</span> <span class="s2">"killFirefox; exit 0"</span> SIGKILL EXIT
<span class="c"># Launch!</span>
docker run --rm --name firefox-karma cypress/browsers:chrome63-ff57 firefox -headless <span class="s2">"</span><span class="nv">$DOCKER_URL</span><span class="s2">"</span>
</code></pre></div>
<p>That shell script just sits in the same directory as our <code>karma.conf.js</code> which has this snippet in it:</p>
<div class="highlight"><pre><code class="language-" data-lang="">browsers: [
__dirname + '/docker-browser-firefox.sh',
// __dirname + '/docker-browser-chrome.sh'
],
</code></pre></div>
<p>I tried getting this working with Chrome headless but only got the following message <code>libudev: udev_has_devtmpfs: name_to_handle_at on /dev: Operation not permitted</code>
even when using <code>-v /dev/shm:/dev/shm</code> as part of the <code>docker run</code> command and <code>--no-sandbox --disable-gpu</code> as part of
the browser args.</p>
<h2 id="summary">Summary</h2>
<p>Voila! Karma tests running in browsers running in Docker :)</p>
<p>This could probably be packaged up in to a nice NPM module, but I figured that all that it would do was replace the shell script so I wasn't entirely convinced the effort was worth it.</p>
Switching from Javascript to Typescript2017-12-07T00:00:00+00:00http://labs.adaptavist.com/code/2017/12/07/switching-from-javascript-to-typescript<h3 id="why">Why?</h3>
<p><del>Because all the cool kids are doing it.</del></p>
<p>Because types.</p>
<p>Specifically: code that is written in a language that permits the declaration of types gives the programmer very useful
information about how the program is supposed to work.</p>
<p>Additionally, and perhaps more importantly, we can now get compile-time assurance that our application works with data
that is in the correct shape/type, and therefore avoid a whole class of runtime bugs.</p>
<p>This is primarily the story of how we enabled usage of Typescript in our ES2017 project, rather than the story of how we
converted the existing ES2017 code to TS. </p>
<h3 id="starting-point">Starting point</h3>
<ul>
<li>~5k lines of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript#JavaScript_versions">ES2017</a> code</li>
<li>~7k lines of JSX/React code</li>
<li>Webpack config with long-term caching and a load of 'common chunks'</li>
<li>Handful of Karma tests</li>
<li>Code coverage in Sonar</li>
</ul>
<p>Additionally, we didn't want to have to convert all the code in one big bang. Unlike <a href="https://www.lucidchart.com/techblog/2017/11/16/converting-600k-lines-to-typescript-in-72-hours/">these guys</a>.</p>
<p>We had been debating whether to use <a href="https://flow.org/">Flow</a> or Typescript for a while, but the catalyst was when a colleague embraced
Typescript on a more internal project and was able to sit down with us and help us through the initial migration/adoption
process.</p>
<p>Interestingly I didn't really know about some of the <a href="https://github.com/Microsoft/TypeScript/issues/9825">differences in approach</a> until after we'd completed this steps
outlined below.</p>
<h3 id="first-steps">First steps</h3>
<p>The first changes we made were configuration changes. We added Typescript and ts-loader as dependencies of our application:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">yarn add --dev typescript ts-loader
</code></pre></div>
<p>We copied the <code>tsconfig.json</code> file from my colleague's internal project and modified it a bit:</p>
<div class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="s2">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"esnext"</span><span class="p">,</span><span class="w">
</span><span class="s2">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"es2015"</span><span class="p">,</span><span class="w">
</span><span class="s2">"lib"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"es2017"</span><span class="p">,</span><span class="w"> </span><span class="s2">"dom"</span><span class="p">,</span><span class="w"> </span><span class="s2">"dom.iterable"</span><span class="p">],</span><span class="w">
</span><span class="s2">"sourceMap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="s2">"allowJs"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="s2">"jsx"</span><span class="p">:</span><span class="w"> </span><span class="s2">"react"</span><span class="p">,</span><span class="w">
</span><span class="s2">"rootDirs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"app"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="s2">"forceConsistentCasingInFileNames"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="s2">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="s2">"noImplicitReturns"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="s2">"noUnusedLocals"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="s2">"exclude"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"node_modules"</span><span class="p">,</span><span class="w">
</span><span class="s2">"build"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<p>And then we updated our Webpack config to load the new files:</p>
<div class="highlight"><pre><code class="language-" data-lang="">/* webpack.config.js */
resolve: {
extensions: ['.ts', '.js', '.jsx'],
modules: [
'node_modules'
],
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
},
...
</code></pre></div>
<p>And finally we renamed a file that contained constants only to be <code>.ts</code> instead of <code>.js</code> - we chose this file because it
would be valid Typescript immediately but was also used by other JS files so it was a good candidate for proving in a
simple way that our build pipeline still worked as expected and we could use TS files from within our JS files.</p>
<h3 id="external-dependencies">External Dependencies</h3>
<p>Thankfully when we next ran our webpack build everything still worked! Encouraged by our great success we picked a file
that had an external module (<a href="https://www.npmjs.com/package/jwt-decode">jwt-decode</a>) dependency, to ensure we could load external type definitions.</p>
<p>We renamed the file, and got some TS compiler errors because the type definitions didn't exist. Thankfully my colleague
knew we could just try adding the following dependency to our project:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">yarn add --dev @types/jwt-decode
</code></pre></div>
<p>That helped, but then we had to actually modify our file that used that library to fix the compile-time bugs - in particular
there were a few places in our code where we could have had trouble accessing properties on a null value.</p>
<h3 id="linting">Linting</h3>
<p>We added <code>tslint</code> and <code>tslint-loader</code> and a tslinting config file, and updated our webpack config to use those:</p>
<div class="highlight"><pre><code class="language-" data-lang="">/* webpack.config.js */
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
enforce: 'pre',
use: [
{
loader: 'tslint-loader'
}
]
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
},
...
</code></pre></div>
<h3 id="nothing-to-report">Nothing to report</h3>
<p>Software is never this straightforward. We were thoroughly impressed with the whole process so far and how painless it
had been.</p>
<p>So we went for lunch.</p>
<h3 id="a-long-dark-afternoon">A long dark afternoon</h3>
<p>Once we got back from lunch, we tried migrating to <a href="https://github.com/aikoven/typescript-fsa">typescript-fsa</a> and all hell broke loose. I don't really remember
all the various errors but they were incomprehensible and numerous and I did not understand them at all. I'm pretty sure,
according to my browser history, that I ended up looking through <a href="https://github.com/Microsoft/TypeScript/blob/master/tests/baselines/reference/callGenericFunctionWithIncorrectNumberOfTypeArguments.errors.txt">this</a>...</p>
<p>Not cool Typescript, not cool. I saved my work on a branch and left it alone.</p>
<h3 id="three-or-four-weeks-later">Three or four weeks later</h3>
<p>I was disappointed that our migration to TS had started off so well and then ended up in a big bag of fail. It took
several weeks to get around to picking up the task again.</p>
<p>I reverted the changes trying to get typescript-fsa working, and instead I took the approach of switching Babel out of our
webpack build entirely and embracing <code>ts-loader</code> completely.</p>
<p>I also did some more reading on the various <a href="https://www.typescriptlang.org/docs/handbook/compiler-options.html">compiler options</a> that we'd copied blindly from my colleague. It turns out
that there are a couple of <em>really important</em> ones to get right...</p>
<h3 id="module-target-and-lib">module, target and lib</h3>
<p>It turns out that our initial config wasn't quite right and we now have this configuration instead:</p>
<div class="highlight"><pre><code class="language-" data-lang="">/* tsconfig.json */
"module": "es2015",
"target": "es5",
"lib": ["es2017", "dom", "dom.iterable"]
</code></pre></div>
<p>I'll talk about <code>moduleResolution</code> later, but suffice to say its <em>very important</em> that you get those 3 config options right.</p>
<h3 id="bye-bye-babel">Bye, bye Babel</h3>
<p>Babel had been great, but I had a feeling that some of my troubles from before had been due to the fact that some of our
app was being loaded/transpiled by Babel and other bits of it by Typescript, so I abandoned the Babel ship:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">yarn remove babel-core babel-loader babel-preset-react babel-preset-es2015 babel-eslint ...
</code></pre></div>
<p>There were a bunch of babel plugins I removed too. Suffice to say we now had a lot less dependencies and our Webpack config
for regular JS/JSX files went from:</p>
<div class="highlight"><pre><code class="language-" data-lang="">loader: 'babel-loader',
options: {
plugins: [
'transform-class-properties',
'transform-promise-to-bluebird',
'transform-object-rest-spread',
'syntax-dynamic-import',
'transform-async-to-generator',
'transform-regenerator',
'transform-runtime'
].concat(production ? [] : 'react-hot-loader/babel'),
presets: [
'react',
['es2015', {modules: false}]
]
}
</code></pre></div>
<p>to:</p>
<div class="highlight"><pre><code class="language-" data-lang="">loader: 'ts-loader'
</code></pre></div>
<p>which was nice :)</p>
<h3 id="need-for-speed">Need for Speed</h3>
<p>I followed the instructions from <a href="https://github.com/TypeStrong/ts-loader">ts-loader</a> to speed up the Typescript processing which worked a treat.</p>
<p>I mentioned <code>moduleResolution</code> earlier and this is the point when I had to reconfigure that tsconfig option.</p>
<p>The way in which the Typescript compilation is sped up is by handing the type-checking process off into its own thread
that runs in parallel to the Webpack bundling. As a consequence, Typescript now needs to resolve modules, as well as Webpack.</p>
<p>The resolution approach we wanted Typescript to take (the <a href="https://www.typescriptlang.org/docs/handbook/module-resolution.html">Node approach</a>) was not the default value that Typescript
selected based on our other configuration options, so we had to explicitly specify the module resolution approach.</p>
<h3 id="karma">Karma...</h3>
<p>It was at this point, if I recall correctly, that I updated essentially all of our test related dependencies and also
switched our karma webpack config from <code>babel-loader</code> to <code>ts-loader</code>. The reason for updating all our test dependencies
(chai, karma, enzyme, etc) was to try and avoid problems due to incompatible or out-of-date libraries.</p>
<p>It look a little time but wasn't hellish enough to ruin my day.</p>
<p>Once complete, thankfully I had a fully Typescript-built application and a set of Karma tests that actually ran
successfully! There was no code coverage, I was willing to sacrifice that for the sake of type safety in our codebase,
albeit in just one or two files.</p>
<h3 id="code-coverage">Code coverage</h3>
<p>This actually took the longest to resolve - primarily because I didn't quite understand how the code coverage process
was actually working and there are <a href="https://www.npmjs.com/package/karma-typescript-plugin">several</a> <a href="https://www.npmjs.com/package/karma-typescript">different</a> <a href="https://www.npmjs.com/package/karma-remap-coverage">packages</a> on NPM that claim to solve the code coverage
problem for Typescript. None of them really solved my problem because we aren't actually running Typescript tests. </p>
<p>We use Karma for testing because a not insignificant amount of our codebase relies indirectly on the DOM existing in order
to execute, and mocking out the relevant parts of the DOM and other browser-provided JS was determined not to be worth
the effort.</p>
<p>Subsequently our Karma build used Webpack and Babel to generate 'chunks' for each test. The Babel configuration included
a plugin called babel-plugin-istanbul that added the relevant code coverage information and the standard coverage reporter
in Karma would generate the LCOV file and HTML report that I wanted.</p>
<p>It was actually <a href="https://www.linkedin.com/pulse/typescript-20-how-get-correct-test-coverage-line-numbers-willem-liu/">this article</a> on LinkedIn that helped fill in the gaps in my understanding, or at least point me in
the right direction.</p>
<p>Given that we are transpiling our code as part of the test run we need to instrument the source at that point in the build
pipeline, so adding the <a href="https://github.com/webpack-contrib/istanbul-instrumenter-loader">istanbul-instrumenter-loader</a> to our Webpack config alongside the ts-loader achieves that goal.</p>
<div class="highlight"><pre><code class="language-" data-lang="">/* webpack.config.js */
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [{
loader: 'cache-loader'
},{
loader: 'thread-loader',
options: {
workers: Math.max(os.cpus().length - 1, 1);
}
},{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
},{
loader: 'istanbul-instrumenter-loader',
options: { esModules: true }
}]
},
...
</code></pre></div>
<p>Additionally, we then need Karma to pick up on the fact that our sources are now annotated, so we add the 'coverage' preprocessor
as well as the webpack preprocessor, and then, magically, everything works OK! (Except the HTML reporter...)</p>
<div class="highlight"><pre><code class="language-" data-lang="">/* karma.conf.js */
reporters: ['junit', 'mocha', 'coverage'],
browsers: [
'PhantomJS',
'FirefoxHeadless'
],
failOnEmptyTestSuite: true,
singleRun: true,
coverageReporter: {
dir: '../build/coverage',
reporters: [
// For browsing - currently only partially works
//{type: 'html', subdir: 'html'},
// For Sonar
{type: 'lcovonly', subdir: '.', file: 'lcov.info'}
]
},
preprocessors: {
'karma/**/*.js': ['webpack', 'coverage'],
'karma/**/*.jsx': ['webpack', 'coverage']
},
</code></pre></div>
<h3 id="summary">Summary</h3>
<ul>
<li>Switch wholesale from using a Babel loader in Webpack to using the Typescript loader.<br>
This was surprisingly straight-forward.</li>
<li>Fix up the tsconfig.json properties <code>module</code>, <code>target</code>, <code>lib</code> and <code>moduleResolution</code>.<br>
This was the most important bit in the whole process I think.</li>
<li>Understand how our tests are being run and how coverage fits into that build pipeline.<br>
Always good to understand the technology you're using.</li>
<li>Write more TS :)</li>
</ul>
Fun with React event handlers2017-10-30T00:00:00+00:00http://labs.adaptavist.com/code/2017/10/30/fun-with-react-event-handlers<h3 id="this-can-39-t-be-happening">This can't be happening</h3>
<p>When your colleague tells you that the UI in your product is no longer saving stuff, that's a low point in the day.</p>
<p>The save button on several pages in our <a href="https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner/cloud/overview">Jira Cloud add-on</a> appeared to do absolutely nothing.</p>
<h3 id="that-shouldn-39-t-happen">That shouldn't happen</h3>
<p>We quickly checked the Developer Tools in our browser for (new) errors. There were none.</p>
<p>A few console log statements later and we realized that the save button does some web form validation and it was erroneously
reporting that the form contents were invalid. A few more log statements later and sure enough, the data being validated
was incorrect, but the form's contents didn't match the data that was being validated. The data being validated was an old
copy of the form data...</p>
<h3 id="why-does-that-happen">Why does that happen?</h3>
<p>We scratched our heads. That makes no sense.</p>
<p>I then remembered something about <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures">functions being closures</a> and variables (or consts, because who doesn't love immutability)
being closed over etc etc... so we looked at the event handier code on the button:</p>
<div class="highlight"><pre><code class="language-jsx" data-lang="jsx">const SecondaryButtons = ({title, onSave, onCancel, saving }) => {
const { inProgress } = saving;
return (
<div className='saveCancelRow'>
<ButtonsWrapper>
<Button
handleClick={onSave}
title={title}
disabled={inProgress}>Save</Button>
<Button
disabled={inProgress}
title='Cancel changes'
handleClick={(e) => {
e.preventDefault();
onCancel();
}}>Revert</Button>
</ButtonsWrapper>
</div>
);
};
// ... code omitted for brevity
const PageForm = connect(mapStateToProps)(({ formData, saving, dispatch }) => {
return (
<Form>
<SecondaryButtons
title='Save page'
saving={saving}
onCancel={() => {
dispatch(revertChangeAction(formData.uuid));
}}
onSave={() => {
dispatch(validateAndSave(formData));
}} />
</Form>
);
});
</code></pre></div>
<p>We quickly changed the code so we pass the form contents (<code>formData</code>) around to make sure we close over the right value.
New console logs added to the SecondaryButtons component to ensure we get the right data in the component.</p>
<div class="highlight"><pre><code class="language-jsx" data-lang="jsx">const SecondaryButtons = ({title, onSave, onCancel, saving, formData }) => {
console.log('DEBUG formData', formData);
const { inProgress } = saving;
return (
<div className='saveCancelRow'>
<ButtonsWrapper>
<Button
handleClick={() => {
console.log('DEBUG handleClick', formData);
onSave(formData);
}}
title={title}
disabled={inProgress}>Save</Button>
<Button
disabled={inProgress}
title='Cancel changes'
handleClick={(e) => {
e.preventDefault();
onCancel(formData.uuid);
}}>Revert</Button>
</ButtonsWrapper>
</div>
);
};
// ... code omitted for brevity
const PageForm = connect(mapStateToProps)(({ formData, saving, dispatch }) => {
return (
<Form>
<SecondaryButtons
formData={formData}
title='Save page'
saving={saving}
onCancel={(uuid) => {
dispatch(revertChangeAction(uuid));
}}
onSave={(data) => {
dispatch(validateAndSave(data));
}} />
</Form>
);
});
</code></pre></div>
<p>Same behaviour. Form is marked as invalid and refuses to submit because the wrong data is being validated.</p>
<p>We add an additional log statement just to make sure that the data the component receives is what is used in the event handler.</p>
<p><strong>It isn't...</strong></p>
<p><em>Wat?</em> We literally just logged that the component gets given the right data. And somehow the old data is used when
validating?! It's like the event handler hasn't been updated...</p>
<p>How could that even happen? We use React and we know the component is being rendered because of our log statement. Right?</p>
<p>Nope.</p>
<blockquote>
<p>"Internally, React uses several clever techniques to minimize the number of costly DOM operations required to update the UI" - <a href="https://reactjs.org/docs/optimizing-performance.html">https://reactjs.org/docs/optimizing-performance.html</a></p>
</blockquote>
<h3 id="oh-i-see">Oh, I see</h3>
<p>There's more at <a href="https://reactjs.org/docs/reconciliation.html">https://reactjs.org/docs/reconciliation.html</a></p>
<p>OK... so maybe one of the main reasons for using React has come back to bite us. React only re-renders the UI when it changes...</p>
<p>In our case, the UI didn't change - the button still says 'Save' each time the form data changes. So only changing the
event handler is not enough to trigger a re-render, because although our component's render function was being executed,
it wasn't returning anything different to what React knew was already rendered...</p>
<h3 id="how-did-that-ever-work">How did that ever work?!</h3>
<p>It didn't... So what's the solution?</p>
<p>An ugly hack is to use a <code>key</code> prop based off the form data contents. At least, that's what we did to get a bug fix deployed.
In the long term we should decouple the validation/saving logic so it pulls the data from our Redux store on demand
instead of relying on the props given to the component at render time.</p>
<p>Remember kids, don't expect React to re-render your components if there are no visible changes to them DOM output! </p>
Switching from Groovy to Java2017-07-17T00:00:00+01:00http://labs.adaptavist.com/code/2017/07/17/switching-from-groovy-to-java<p>For those of you who are aware that <a href="https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner/cloud/overview">ScriptRunner</a> allows JIRA users to execute Groovy code in response to events and
workflow transitions, it may come as a surprise that the ScriptRunner for JIRA Cloud team has been converting the
majority of our Cloud service codebase from Groovy to Java.</p>
<p>In this blog post I'll talk a little bit about the reasons for that change and the things we miss from Groovy.</p>
<h2 id="1-static-compilation-finds-bugs-before-runtime">1. Static compilation finds bugs before runtime</h2>
<p>Very early on in the development of ScriptRunner for JIRA Cloud we enabled <a href="http://docs.groovy-lang.org/latest/html/gapi/groovy/transform/CompileStatic.html">static compilation</a> of our Groovy code
because we shipped some type-related bugs into production. The CompileStatic annotation tells the Groovy compiler to
type-check our code at compile time, so as to avoid silly mistakes!</p>
<h2 id="2-the-groovy-compiler-isn-39-t-as-good-at-static-compilation-as-the-java-compiler">2. The Groovy compiler isn't as good at static compilation as the Java compiler</h2>
<p>Having enable static compilation we started running into scenarios where the Groovy compiler didn't know what type a
<code>def</code> was even though it was extremely clear to us and our IDE. Our codebase was becoming littered with <code>as Map</code> or
<code>as String</code> and using <code>def</code> was becoming pointless. Given the amount of type-hinting we were doing, we felt like
switching to Java wouldn't add any additional type indication overhead.</p>
<h2 id="3-we-like-our-types-even-if-they-39-re-verbose">3. We like our types, even if they're verbose</h2>
<p>One of the appeals of Groovy is that you don't have to specify the type of a variable when the compiler can figure it
out for you. Having said that, both Groovy and Java are strongly typed languages, and code is for humans, not for
computers, so hiding the type of any given variable seems to be deliberately making things hard for ourselves. For this
same reason we're considering a move to TypeScript from ES6.</p>
<h2 id="4-intellij-idea-didn-39-t-handle-some-of-the-generics-in-our-groovy-code">4. IntelliJ IDEA didn't handle some of the generics in our Groovy code</h2>
<p>Finally, our IDE just couldn't handle the generics in some of our Ratpack code and subsequently highlighted huge chunks
of our source incorrectly as having the wrong return type. This obscured actual errors in those sections of code which
made working in any promise based Groovy code a real PITA.</p>
<h2 id="so-what-do-we-miss-from-groovy">So, what do we miss from Groovy?</h2>
<ol>
<li><code>@Slf4j</code> which injected a logger into each class for us, although we might introduce <code>@Log4j</code> from <a href="https://projectlombok.org/features/log">Project Lombok</a></li>
<li>Succinct map and list literals although this might change a little with Java 9</li>
<li>Some of the collection based default methods required less code than Java 8s streams, but the map/reduce terminology<br>
in Java is much more accessible than Groovys collect/inject</li>
<li>String interpolation, although we do prefer only having one String type</li>
</ol>
<p>We're still using Groovy for our tests though, in part because we like Spock and in part because static compilation isn't as necessary there :)</p>
Useful Git Aliases2017-05-24T00:00:00+01:00http://labs.adaptavist.com/git/2017/05/24/useful-git-aliases<p>I get the impression that Git is still quite misunderstood, and newcomers to Git (myself included) struggle to pick up
which commands are actually useful and which ones were just designed by <a href="https://git-scm.com/book/en/v2/Getting-Started-A-Short-History-of-Git">Linus</a> to invoke fear, uncertainty and doubt.</p>
<p>Over the last few years I've come up with (and stolen) a few Git command aliases that make my day-to-day usage of Git on
the command line an intuitive/fluent experience.</p>
<p>If you've been using Git for a bit, but don't really <a href="https://en.wikipedia.org/wiki/Grok">grok</a> it, I'd recommend watching <a href="https://www.youtube.com/watch?v=sevc6668cQ0&list=PLRsbF2sD7JVrnrqNopuT9GnkOSbFKtRvb&index=151">Knowledge is Power: Getting out of trouble by understanding Git</a> if you haven't already.</p>
<h2 id="tl-dr">TL;DR</h2>
<p>Here's my Git alias list from <code>~/.gitconfig</code>.</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">[</span><span class="nb">alias</span><span class="o">]</span>
co <span class="o">=</span> checkout
cm <span class="o">=</span> checkout master
cb <span class="o">=</span> checkout -b
st <span class="o">=</span> status
staged <span class="o">=</span> diff --staged
unadd <span class="o">=</span> reset HEAD
save <span class="o">=</span> commit -m
amend <span class="o">=</span> commit --amend
discard <span class="o">=</span> checkout --
fp <span class="o">=</span> !git fetch -a --tags <span class="o">&&</span> git pull
please <span class="o">=</span> push --force-with-lease
graph <span class="o">=</span> log --oneline --all --graph --decorate
oneline <span class="o">=</span> log --oneline -n 20
hist <span class="o">=</span> log -n 20 --date<span class="o">=</span>iso --pretty<span class="o">=</span>format:<span class="s1">'%C(yellow)%h%Creset %cd %Cblue%an <%ae>%Creset %s'</span>
refresh <span class="o">=</span> <span class="s2">"!f() { CBRANCH=</span><span class="k">$(</span>git rev-parse --abbrev-ref HEAD<span class="k">)</span><span class="s2">; git cm && git fp && git co </span><span class="se">\"</span><span class="nv">$CBRANCH</span><span class="se">\"</span><span class="s2"> && git rebase master; }; f"</span>
put <span class="o">=</span> <span class="s2">"!f() { CBRANCH=</span><span class="k">$(</span>git rev-parse --abbrev-ref HEAD<span class="k">)</span><span class="s2">; git push -u origin </span><span class="se">\"</span><span class="nv">$CBRANCH</span><span class="se">\"</span><span class="s2">; }; f"</span>
</code></pre></div>
<h2 id="downloading-a-repository">Downloading a repository</h2>
<blockquote>
<p>git clone <repository url here></p>
</blockquote>
<p>We're literally cloning the entire contents of the remotely hosted repository.</p>
<p>In your terminal, switch into the directory that you want your repository downloaded into, then copy & paste the SSH or
HTTPS url for the repository and run this command.</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd</span> ~/Code
git clone git@github.com:facebook/react.git
<span class="nb">cd </span>react
</code></pre></div>
<p>Now you're in the directory for your local copy of the React project.</p>
<p>See <a href="https://git-scm.com/docs/git-clone">git clone</a> for more options.</p>
<h2 id="creating-a-branch">Creating a branch</h2>
<blockquote>
<p>git checkout -b <your branch name here></p>
</blockquote>
<p>I always make changes on a feature branch, and we enforce branch naming conventions too, so my workflow looks like:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git checkout -b bugfix/PROJECTKEY-123-fix-all-the-things
</code></pre></div>
<p>However, <code>checkout -b</code> is a lot of typing, so I use this alias in my <code>~/.gitconfig</code> file:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">[</span><span class="nb">alias</span><span class="o">]</span>
cb <span class="o">=</span> checkout -b
</code></pre></div>
<p>All further aliases in this blog post can be added below the <code>[alias]</code> section of your <code>~/.gitconfig</code> file.</p>
<p>So I can type:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git cb feature/PROJECTKEY-456-awesome-new-sauce
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-checkout">git checkout</a> for more options.</p>
<h2 id="checking-repository-status">Checking repository status</h2>
<blockquote>
<p>git status</p>
</blockquote>
<p>I use this A LOT. This command will tell you useful information about the files in your local copy of the repository,
like which files are modified, which files are staged ready to commit, etc.</p>
<p>It also prints out some useful commands as hints for what you might want to do next.</p>
<p>Obviously, <code>status</code> is a lot of typing, so I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">st <span class="o">=</span> status
</code></pre></div>
<p>So I can type:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git st
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-status">git status</a> for more options.</p>
<h2 id="switching-branch">Switching branch</h2>
<blockquote>
<p>git checkout <branch name here></p>
</blockquote>
<p>If you have no unstaged changes (see below) in your directory, you can just 'checkout' the branch you're interested in.</p>
<p>I use these aliases:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">co <span class="o">=</span> checkout
cm <span class="o">=</span> checkout master
</code></pre></div>
<p>So that I type:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git co bugfix/FOO-123-everything-is-on-fire
<span class="c"># now I'm on that fire related branch</span>
git cm
<span class="c"># now I'm on the master branch</span>
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-checkout">git checkout</a> for more options.</p>
<h2 id="stage-changes-ready-for-a-commit">Stage changes ready for a commit</h2>
<blockquote>
<p>git add -p</p>
</blockquote>
<p>This command allows you to pick and chose which changes you've made should be staged for a commit. It will show you a
diff of each partial change and ask if you want to stage that change or not.</p>
<p>Press y for YES and n for NO or q for QUIT when it asks you if you want to Stage this hunk. If you just press enter it
gives you a whole load more info about each response option.</p>
<p>I don't have an alias for this.</p>
<p>I usually run <code>git st</code> (see above) after I've run <code>git add -p</code> to sanity check I've staged the changes I think I have!</p>
<p>I also use <code>git staged</code> (below) to complete my sanity check.</p>
<p>See <a href="https://git-scm.com/docs/git-add">git add</a> for more options.</p>
<h2 id="reviewing-staged-changes-before-committing">Reviewing staged changes before committing</h2>
<blockquote>
<p>git diff --staged</p>
</blockquote>
<p>This shows a diff of all the changes I've staged for the commit.</p>
<p>I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">staged <span class="o">=</span> diff --staged
</code></pre></div>
<p>So that I can type:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git staged
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-diff">git diff</a> for more options.</p>
<h2 id="un-staging-changes">Un-staging changes</h2>
<blockquote>
<p>git reset HEAD -p</p>
</blockquote>
<p>I often stage a change to a file that I don't actually want to commit. I have fat fingers.</p>
<p>This handy command does the exact opposite to the <code>git add -p</code> command listed above, it asks you bit by bit whether you
want to remove a change from staging.</p>
<p>I use this alias, because its like the synonym for <code>git add</code>:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">unadd <span class="o">=</span> reset HEAD
</code></pre></div>
<p>And then type this command when I want to remove staged changes:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git unadd -p
</code></pre></div>
<p><strong>NB - this does not remove those changes from disk, it just removes them from being staged for a commit.</strong></p>
<p>See <a href="https://git-scm.com/docs/git-reset">git reset</a> for more options.</p>
<h2 id="making-a-commit">Making a commit</h2>
<blockquote>
<p>git commit -m "<your commit message here>"</p>
</blockquote>
<p>Once I'm happy with the changes I've staged for a commit, this is how I make a commit.</p>
<p>In my pursuit of reduced typing I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">save <span class="o">=</span> commit -m
</code></pre></div>
<p>So my commands look like:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git save <span class="s2">"Updates..."</span>
</code></pre></div>
<p><strong>Unlike that example, I highly recommend you <a href="https://chris.beams.io/posts/git-commit/">write good commit messages</a>.</strong></p>
<p>See <a href="https://git-scm.com/docs/git-commit">git commit</a> for more options.</p>
<h2 id="viewing-commits">Viewing commits</h2>
<blockquote>
<p>git log -n 10 --oneline<br>
git log -n 10 --oneline -p<br>
git log -n 10 --date=iso --pretty-format:'%C(yellow)%h%Creset %cd %Cblue%an <%ae>%Creset %s'<br>
git log --oneline --all --graph --decorate</p>
</blockquote>
<p>I use a combination of those commands or their respective aliases that I've set up in order to see what a mess I've made
of my Git repository before pushing changes to a remote Git server. </p>
<p>The first command there is fairly simple, one-commit-per-line output containing just the hash and the commit message.</p>
<p>The second command shows the diff that the commit introduces.</p>
<p>The third command shows the author and commit timestamp as well as the hash and commit message.</p>
<p>The fourth command shows a pretty graph view of the repository and how the branches interact.</p>
<p>I use these aliases, in this order of frequency:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">hist <span class="o">=</span> log -n 10 --date<span class="o">=</span>iso --pretty<span class="o">=</span>format:<span class="s1">'%C(yellow)%h%Creset %cd %Cblue%an <%ae>%Creset %s'</span>
oneline <span class="o">=</span> log --oneline -n 10
graph <span class="o">=</span> log --oneline --all --graph --decorate
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-log">git log</a> for more options.</p>
<h2 id="amending-commits">Amending commits</h2>
<blockquote>
<p>git commit --amend</p>
</blockquote>
<p>When I'm not writing perfect code, I find a bug or typo or something after I've make a commit. This command allows me to
stage a new change and then add that to the previous commit.</p>
<p>(This commit actually replaces the previous commit with a new one that is a merge of the new and previous changes.)</p>
<p>I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">amend <span class="o">=</span> commit --amend
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-commit">git commit</a> for more options.</p>
<h2 id="deleting-changes-i-don-39-t-want">Deleting changes I don't want</h2>
<blockquote>
<p>git checkout -- <file path here></p>
</blockquote>
<p>Once I've committed my changes, if there are edits I've made to files that I really don't need any more, I run this command
to remove those changes from disk. This saves me opening each altered file and doing CTRL-Z or manually undoing those
un-needed changes.</p>
<p>I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">discard <span class="o">=</span> checkout --
</code></pre></div>
<p>Which can be used like this:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git discard src/main/java/com/acme/foo/bar/utils/DefaultServiceInstanceFactoryManagerBuilderImpl.java
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-checkout">git checkout</a> for more options.</p>
<h2 id="pushing-commits-to-the-remote-git-server">Pushing commits to the remote Git server</h2>
<h3 id="new-branches">New branches</h3>
<blockquote>
<p>git push -u origin <your branch name here></p>
</blockquote>
<p>This tells Git where on the remote Git server (referenced by the word 'origin' which is just the default name for the
original remote server you cloned the repository from) to put your branch. It is possible to have a branch called one
thing locally and another thing on the remote server. </p>
<p>I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">put <span class="o">=</span> <span class="s2">"!f() { CBRANCH=</span><span class="k">$(</span>git rev-parse --abbrev-ref HEAD<span class="k">)</span><span class="s2">; git push -u origin </span><span class="se">\"</span><span class="nv">$CBRANCH</span><span class="se">\"</span><span class="s2">; }; f"</span>
</code></pre></div>
<p>So that saves me copy and pasting the branch name.</p>
<h3 id="existing-branches">Existing branches</h3>
<blockquote>
<p>git push</p>
</blockquote>
<p>If your branch existing on the remote server already, just use this. No alias required.</p>
<h3 id="rebased-branch-or-a-branch-with-amended-commits">Rebased branch or a branch with amended commits</h3>
<blockquote>
<p>git push --force-with-lease</p>
</blockquote>
<p>If you've rebased (see below) or amended any commits that you've already pushed to a remote Git server, this tells Git
to override whatever is already on the remote server.</p>
<p>I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">please <span class="o">=</span> push --force-with-lease
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-push">git push</a> for more options.</p>
<h2 id="fetching-changes-from-the-remote-git-server">Fetching changes from the remote Git server</h2>
<blockquote>
<p>git fetch -a --tags<br>
git pull</p>
</blockquote>
<p>The first command there just asks the remote server for the most up-to-date metadata about the repository e.g. which
commits are on which branches, what branches and tags exist.</p>
<p>The second command there downloads the changes from the remote server that correspond to the branch you're currently on.</p>
<p>I use this alias:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">fp <span class="o">=</span> !git fetch -a --tags <span class="o">&&</span> git pull
</code></pre></div>
<p>See <a href="https://git-scm.com/docs/git-fetch">git fetch</a> and <a href="https://git-scm.com/docs/git-pull">git pull</a> for more options.</p>
<h2 id="rebasing-from-master">Rebasing from master</h2>
<blockquote>
<p><strong>If someone else has already checked out your branch or has made changes to it as well, DO NOT rebase the branch
or amend commits because then you will have different and incompatible versions of the same branch which will require
one of you to delete your local branch completely.</strong></p>
</blockquote>
<p>This requires a few steps:</p>
<blockquote>
<p>git checkout master<br>
git fetch -a --tags<br>
git pull<br>
git checkout -<br>
git rebase master</p>
</blockquote>
<p>Often, you'll be working on a branch and changes will happen on the master branch whilst your working. You have two
options on how to reconcile those changes with the changes you have made:</p>
<ul>
<li>Merge master into your branch</li>
<li>Rebase your branch off master</li>
</ul>
<p>We typically prefer rebasing for a couple of reasons:</p>
<ul>
<li>it doesn't introduce lots of additional merge commits</li>
<li>it avoids some edge-cases where commits can get lost</li>
<li>it makes the Git history a bit more clear, even if it no longer reflects how/when the work was actually done</li>
</ul>
<p>You can think of rebasing as taking some commits and re-applying the changes that those commits hold elsewhere, so that
new commits get generated that look very similar to the original ones.</p>
<p>Whilst rebasing you may need to resolve conflicts in the same way that you would when merging.</p>
<p>I use this alias which uses a few of the other aliases too:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">refresh <span class="o">=</span> <span class="s2">"!f() { CBRANCH=</span><span class="k">$(</span>git rev-parse --abbrev-ref HEAD<span class="k">)</span><span class="s2">; git cm && git fp && git co </span><span class="se">\"</span><span class="nv">$CBRANCH</span><span class="se">\"</span><span class="s2"> && git rebase master; }; f"</span>
</code></pre></div>
<p>That alias will obtain the current branch name, switch to master, fetch the latest remote changes, download them for the
master branch, switch back to the branch I was on and then rebase the branch off of the up-to-date master branch.</p>
<h2 id="summary">Summary</h2>
<p>My typical workflow looks something like this:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash">git cm <span class="c"># Start from the master branch</span>
git fp <span class="c"># Get the latest changes</span>
git cb bugfix/PROJ-123-ui-is-broken <span class="c"># New branch please</span>
<span class="c"># Edit files here</span>
git st <span class="c"># What have I changed?</span>
git add -p <span class="c"># Stage some changes</span>
git staged <span class="c"># What have I staged?</span>
git unadd -p <span class="c"># Oops, I shouldn't have staged that!</span>
git st <span class="c"># What's the latest state of things?</span>
git add -p <span class="c"># Stage some other change</span>
git diff <span class="c"># What have I modified that I haven't staged?</span>
git staged <span class="c"># Is this everything I need for this feature?</span>
git st <span class="c"># Does this look about right?</span>
git save <span class="s2">"UI is not broken"</span> <span class="c"># Make a commit</span>
<span class="c"># Edit the test files I forgot about</span>
git add -p <span class="c"># Stage changes to the tests, ready for commit</span>
git amend <span class="c"># Pretend I fixed the tests as part of the original commit</span>
git discard <span class="c"># Discard other changes I made but don't need</span>
git put <span class="c"># Push the new branch to the remote server</span>
git refresh <span class="c"># Rebase off and updated master branch</span>
git please <span class="c"># Overwrite the branch contents I pushed a moment ago</span>
</code></pre></div>
<p>And then I create a Pull Request in Bitbucket Server, ready for review!</p>
Practical Ratpack Promises2017-03-27T00:00:00+01:00http://labs.adaptavist.com/code/2017/03/27/practical-ratpack-promises<p>There are a lot of excellent resources describing how <a href="http://ldaley.com/post/97376696242/ratpack-execution-model-part-1">Ratpack works</a> and explaining its <a href="http://ldaley.com/post/102495950257/ratpacks-execution-model-in-practice">execution model</a> in practice,
and <a href="http://naleid.com/blog/2016/05/01/ratpack-executions-async-plus-serial-not-parallel">with examples</a> too. There's also the <a href="https://ratpack.io/manual/current/async.html">official documentation</a>.</p>
<p>However, articles like <a href="http://beckje01.com/blog/2014/09/10/ratpack-promise/">this one</a> only scratch the surface of Promises and are also 2 years old at the time of writing,
so I wanted to write about Promises in <a href="https://ratpack.io/">Ratpack</a> from a practical perspective, having used them now for several months.</p>
<h2 id="what-39-s-a-promise">What's a Promise?</h2>
<p>A Promise is an object that will eventually yield or provide a value - often for an asynchronous computation e.g. some
kind of network request. Promises are implemented with a fluent interface that allow you to build a chain of actions that
will occur when the value of the Promise is yielded. Promises are implemented in <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise">Javascript</a>, in Java as <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html">Futures</a>
and in other programming languages like C++, Python, Scala and R.</p>
<p>It's important to note that Promises don't guarantee that a value will ever be yielded, and additionally, they may fail
to provide a value. In Ratpack, a failed/errored Promise is caused by an Exception.</p>
<h2 id="so-how-do-i-use-a-ratpack-promise">So, how do I use a Ratpack Promise?</h2>
<p>Well, a simple if unrealistic example is as follows:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Promise</span><span class="o">.</span><span class="na">value</span><span class="o">(</span><span class="s2">"Hello World"</span><span class="o">).</span><span class="na">then</span> <span class="o">{</span> <span class="n">String</span> <span class="n">message</span> <span class="o">-></span>
<span class="n">println</span> <span class="n">message</span>
<span class="o">}</span>
</code></pre></div>
<p>The <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#value-T-">Promise.value</a> static method call creates a Promise that has a value that is immediately available, which makes
this example slighly unrealistic, although it can be a useful method for testing and caching.</p>
<p>The <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#then-ratpack.func.Action-">.then</a> method call on the Promise is what triggers the Promise to be evaluated.</p>
<blockquote>
<p>If <code>.then</code> is not called then the code inside the Promise that calculates the value is not called.</p>
</blockquote>
<p><a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#then-ratpack.func.Action-">.then</a> also returns void, so you can't chain any additional method calls.</p>
<h2 id="what-about-a-real-world-example-then">What about a real-world example then?</h2>
<p>As I hinted at above, most of the real-world examples are to do with asynchronous calculations which often include some
kind of I/O like a network request.</p>
<p>In the work I've been doing recently we use the <a href="https://aws.amazon.com/sdk-for-java/">AWS Java</a> library a lot, so I'll use that in the examples here. The
AWS library doesn't know about Ratpack's Promises, but it does perform network requests so we wrap the library calls like
this:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>You can read more about <code>Blocking.get</code> in the <a href="https://ratpack.io/manual/current/async.html">documentation</a>, but it returns a Promise for the value returned by the
nested API call. In this case it returns us a Promise for the <a href="http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/model/CreateTableResult.html">CreateTableResult</a> object that Amazon's DynamoDB
client returns.</p>
<p>So, a real world example of using Promises inside of a Ratpack request handler could be:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="kt">void</span> <span class="nf">handle</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">def</span> <span class="n">createTableRequest</span> <span class="o">=</span> <span class="n">buildTableRequest</span><span class="o">(</span><span class="n">context</span><span class="o">)</span>
<span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>For brevity I'm going to mostly omit the <code>void handle(Context context)...</code> stuff from the following examples.</p>
<h2 id="what-happens-if-there-39-s-a-bug-or-the-internet-breaks">What happens if there's a bug, or the Internet breaks?</h2>
<p>Promises get broken. The thing that returns or calculates the value we've been promised may break, sometimes because
we provide invalid data, sometimes because we haven't authenticated ourselves successfully, sometimes because we write
buggy code, sometimes because the network infrastructure we're running on lets us down and sometimes because the external
service we interact with has some internal problem of its own.</p>
<p>Ratpack provides a couple ways of dealing with Promises that fail.</p>
<h3 id="onerror">onError</h3>
<p>We can handle all Exceptions using <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#onError-ratpack.func.Predicate-ratpack.func.Action-">.onError</a>:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">onError</span> <span class="o">{</span> <span class="n">Throwable</span> <span class="n">t</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">t</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"Sorry the table creation failed"</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>Or we can handle only specific Exceptions too:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">onError</span><span class="o">(</span><span class="n">ResourceInUseException</span><span class="o">,</span> <span class="o">{</span> <span class="n">ResourceInUseException</span> <span class="n">e</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"That table already exists!"</span><span class="o">)</span>
<span class="o">})</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<blockquote>
<p>It's important to note that once <code>.onError</code> has been called, nothing else in the promise chain is called. It's a terminal
operation.</p>
</blockquote>
<p>It's also important to note that the location of your error handling methods within the promise chain is significant.
Exceptions will only be handled by the onError (or mapError etc) methods if the error has occurred earlier in the promise
chain. Here's an example:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="c1">// Let's assume that buildTableRequest returns Promise<CreateTableRequest> for this example
</span><span class="n">buildTableRequest</span><span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="n">onError</span> <span class="o">{</span> <span class="n">Exception</span> <span class="n">e</span> <span class="o">-></span>
<span class="c1">// This onError closure will only handle errors from promise returned by the buildTableRequest method
</span> <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s2">"The request was invalid"</span><span class="o">,</span> <span class="n">e</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">clientError</span><span class="o">(</span><span class="n">HttpStatus</span><span class="o">.</span><span class="na">SC_BAD_REQUEST</span><span class="o">)</span>
<span class="o">}</span> <span class="n">flatMap</span> <span class="o">{</span> <span class="n">CreateTableRequest</span> <span class="n">createTableRequest</span> <span class="o">-></span>
<span class="c1">// I'll explain flatMap below
</span> <span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span> <span class="n">onError</span><span class="o">(</span><span class="n">ResourceInUseException</span><span class="o">,</span> <span class="o">{</span> <span class="n">ResourceInUseException</span> <span class="n">e</span> <span class="o">-></span>
<span class="c1">// This onError closure will only handle ResourceInUseException from between the
</span> <span class="c1">// previous onError handler and this point here
</span> <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"That table already exists!"</span><span class="o">)</span>
<span class="o">})</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="maperror">mapError</h3>
<p>Sometimes, certain errors are OK and we can recover from them. In this scenario we can use <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#mapError-ratpack.func.Function-">.mapError</a> to convert our
exception into some valid object that the rest of our code can consume.</p>
<p>Like <code>.onError</code> we can map all exceptions or we can map specific exceptions.</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">mapError</span><span class="o">(</span><span class="n">ResourceInUseException</span><span class="o">,</span> <span class="o">{</span> <span class="n">ResourceInUseException</span> <span class="n">e</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">)</span>
<span class="k">new</span> <span class="nf">CreateTableResult</span><span class="o">()</span>
<span class="o">.</span><span class="na">withTableDescription</span><span class="o">(</span>
<span class="k">new</span> <span class="nf">TableDescription</span><span class="o">()</span>
<span class="o">.</span><span class="na">withTableArn</span><span class="o">(</span><span class="s2">"some-default-value"</span><span class="o">)</span>
<span class="o">.</span><span class="na">withTableStatus</span><span class="o">(</span><span class="n">TableStatus</span><span class="o">.</span><span class="na">ACTIVE</span><span class="o">))</span>
<span class="o">})</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="flatmaperror">flatMapError</h3>
<p>If our error mapping code interacts with a service that returns a promise, then we need to use the <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#flatMapError-ratpack.func.Function-">.flatMapError</a>
method, otherwise we'll end up with a promise within a promise.</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">flatMapError</span><span class="o">(</span><span class="n">ResourceInUseException</span><span class="o">,</span> <span class="o">{</span> <span class="n">ResourceInUseException</span> <span class="n">e</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">)</span>
<span class="c1">// Let's assume this method call returns Promise<CreateTableResult>
</span> <span class="n">getOriginalCreateTableResultFromExternalCache</span><span class="o">(</span><span class="n">context</span><span class="o">)</span>
<span class="o">})</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>If we didn't use <code>.flatMapError</code> in the example above, then we'd end up with something like this:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="cm">/**
* DONT DO THIS
*/</span>
<span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">mapError</span><span class="o">(</span><span class="n">ResourceInUseException</span><span class="o">,</span> <span class="o">{</span> <span class="n">ResourceInUseException</span> <span class="n">e</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">)</span>
<span class="c1">// Let's assume this method call returns Promise<CreateTableResult>
</span> <span class="n">getOriginalCreateTableResultFromExternalCache</span><span class="o">(</span><span class="n">context</span><span class="o">)</span>
<span class="o">})</span> <span class="n">then</span> <span class="o">{</span> <span class="n">Promise</span><span class="o"><</span><span class="n">CreateTableResult</span><span class="o">></span> <span class="n">promise</span> <span class="o">-></span>
<span class="n">promise</span><span class="o">.</span><span class="na">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<h2 id="what-if-i-need-to-change-the-promised-value">What if I need to change the promised value?</h2>
<p>Very often, the value returned by the services and APIs you use don't return the data you want in the format you want
it to be in. In order to change one promised value to another you can use the following methods:</p>
<h3 id="map">map</h3>
<p>For straight forward object manipulation, <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#map-ratpack.func.Function-">.map</a> is your friend:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">map</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">result</span><span class="o">.</span><span class="na">tableDescription</span><span class="o">.</span><span class="na">tableArn</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">String</span> <span class="n">tableArn</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="flatmap">flatMap</h3>
<p>If our value conversion depends on some other promised value, we can use <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#flatMap-ratpack.func.Function-">.flatMap</a> like in the error handling example
above:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">flatMap</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="c1">// Let's assume this method call returns Promise<TableDescription>
</span> <span class="n">externalCache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">result</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">TableDescription</span> <span class="n">description</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${description.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>And of course we can chain these too:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="c1">// import static ratpack.jackson.Jackson.fromJson
</span>
<span class="n">context</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">fromJson</span><span class="o">(</span><span class="n">Map</span><span class="o">))</span> <span class="n">map</span> <span class="o">{</span> <span class="n">Map</span> <span class="n">tableDetails</span> <span class="o">-></span>
<span class="c1">// This method call synchronously returns a CreateTableRequest
</span> <span class="n">buildCreateTableRequest</span><span class="o">(</span><span class="n">tableDetails</span><span class="o">)</span>
<span class="o">}</span> <span class="n">flatMap</span> <span class="o">{</span> <span class="n">CreateTableRequest</span> <span class="n">tableRequest</span> <span class="o">-></span>
<span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="mapif-flatmapif">mapIf / flatMapIf</h3>
<p>There are also some conditional mapping operations you can use on a promise: <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#mapIf-ratpack.func.Predicate-ratpack.func.Function-">.mapIf</a> and <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#flatMapIf-ratpack.func.Predicate-ratpack.func.Function-">.flatMapIf</a></p>
<p>I haven't used these personally, but they are part of Ratpack's Promise API.</p>
<h2 id="what-if-i-just-want-to-intercept-the-promised-value">What if I just want to intercept the promised value?</h2>
<p>We use these often for logging, or for fire-and-forget operations.</p>
<h3 id="next">next</h3>
<p>The <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#next-ratpack.func.Action-">.next</a> and <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#nextOp-ratpack.func.Function-">.nextOp</a> calls can be used to do some work using the current value of the Promise at that point
in the Promise chain, without changing the value of the Promise.</p>
<p>If the Promise has failed, this call does not get invoked, so in the code below, we'll only log request timings for
successful requests.</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="kt">def</span> <span class="n">start</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span>
<span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">next</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="c1">// This code will not be invoked if the dynamoDbClient.createTable call threw an exception
</span> <span class="kt">def</span> <span class="n">taken</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">-</span> <span class="n">start</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s2">"Table creation request for {} took {}ms"</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">tableDescription</span><span class="o">.</span><span class="na">tableArn</span><span class="o">,</span> <span class="n">taken</span><span class="o">)</span>
<span class="o">}</span> <span class="n">onError</span> <span class="o">{</span> <span class="n">Throwable</span> <span class="n">t</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">t</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"Sorry the table creation failed"</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="wiretap">wiretap</h3>
<p>The <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#wiretap-ratpack.func.Action-">.wiretap</a> call, in contrast to <code>.next</code>, does get called regardless of the state of the Promise. Instead of being
passed the value of the Promise at that point in the chain, it gets a <a href="https://ratpack.io/manual/current/api/ratpack/exec/Result.html">Result</a> wrapper which can be inspected to see
the state of the Promise.</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="kt">def</span> <span class="n">start</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span>
<span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">wiretap</span> <span class="o">{</span> <span class="n">Result</span><span class="o"><</span><span class="n">CreateTableResult</span><span class="o">></span> <span class="n">result</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">taken</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">-</span> <span class="n">start</span>
<span class="k">if</span> <span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">isError</span><span class="o">())</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s2">"Table creation request failed after {}ms"</span><span class="o">,</span> <span class="n">taken</span><span class="o">)</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s2">"Table creation request for {} took {}ms"</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">tableDescription</span><span class="o">.</span><span class="na">tableArn</span><span class="o">,</span> <span class="n">taken</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span> <span class="n">onError</span> <span class="o">{</span> <span class="n">Throwable</span> <span class="n">t</span> <span class="o">-></span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s2">"Table creation request failed"</span><span class="o">,</span> <span class="n">t</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"Sorry the table creation failed"</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>We use <code>.wiretap</code> along with <a href="https://ratpack.io/manual/current/api/ratpack/exec/util/ParallelBatch.html#yieldAll--">ParallelBatch.yieldAll()</a> to determine which of our parallel promises failed.</p>
<h2 id="i-want-to-combine-the-promised-value-with-another-value">I want to combine the promised value with another value</h2>
<h3 id="left-right">left / right</h3>
<p>These two methods allow us to convert our promised value into a <a href="https://ratpack.io/manual/current/api/ratpack/func/Pair.html">Pair</a> of values. The <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#left-ratpack.func.Function-">.left</a> method allows us to
specify what will be on the 'left' of the Pair (the original Promised value will be on the right), and the <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#right-ratpack.func.Function-">.right</a> method
allows us to specify what will be on the 'right' of the Pair (the original Promised value will be on the left).</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">left</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">result</span><span class="o">.</span><span class="na">tableDescription</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">Pair</span><span class="o"><</span><span class="n">TableDescription</span><span class="o">,</span> <span class="n">CreateTableResult</span><span class="o">></span> <span class="n">pair</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">tableDesc</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="na">left</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is: ${tableDesc.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>or we could combine this with the <code>.map</code> call from earlier:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">map</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="n">result</span><span class="o">.</span><span class="na">tableDescription</span>
<span class="o">}</span> <span class="n">right</span> <span class="o">{</span> <span class="n">TableDescription</span> <span class="n">desc</span> <span class="o">-></span>
<span class="n">desc</span><span class="o">.</span><span class="na">tableArn</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">Pair</span><span class="o"><</span><span class="n">TableDescription</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">pair</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">tableDesc</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="na">left</span>
<span class="kt">def</span> <span class="n">tableArn</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="na">right</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The ARN for ${tableDesc.tableName} is: ${tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="flatleft-flatright">flatLeft / flatRight</h3>
<p>The <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#flatLeft-ratpack.func.Function-">.flatLeft</a> and <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#flatRight-ratpack.func.Function-">.flatRight</a> methods operate in the same way as the <code>.left</code> and <code>.right</code> calls, but are used
when you want to call some method that returns a Promise, and add the value of that Promise into the Pair.</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span> <span class="n">flatRight</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">attributes</span> <span class="o">=</span> <span class="o">[</span><span class="nl">created:</span> <span class="k">new</span> <span class="n">AttributeValue</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="k">as</span> <span class="n">String</span><span class="o">)]</span>
<span class="kt">def</span> <span class="n">createdItemRequest</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PutItemRequest</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">tableDescription</span><span class="o">.</span><span class="na">tableName</span><span class="o">,</span> <span class="n">attributes</span><span class="o">)</span>
<span class="c1">// This Blocking.get call will return Promise<PutItemResult>
</span> <span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">putItem</span><span class="o">(</span><span class="n">createdItemRequest</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">Pair</span><span class="o"><</span><span class="n">CreateTableResult</span><span class="o">,</span> <span class="n">PutItemResult</span><span class="o">></span> <span class="n">pair</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">tableDescription</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="na">left</span><span class="o">.</span><span class="na">tableDescription</span>
<span class="kt">def</span> <span class="n">putItemResult</span> <span class="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="na">right</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s2">"Consumed capacity: {}"</span><span class="o">,</span> <span class="n">putItemResult</span><span class="o">.</span><span class="na">consumedCapacity</span><span class="o">.</span><span class="na">capacityUnits</span><span class="o">)</span>
<span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The ARN for ${tableDescription.tableName} is: ${tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h2 id="i-want-to-bail-out-of-my-promise-chain-early">I want to bail out of my promise chain early</h2>
<p>As well as the <code>.then</code> and <code>.onError</code> terminating parts of the Promise chain, you can also specify that the chain should
stop processing using a <a href="https://ratpack.io/manual/current/api/ratpack/exec/Promise.html#route-ratpack.func.Predicate-ratpack.func.Action-">.route</a> method call which takes a predicate and an action.</p>
<blockquote>
<p>If the predicate for a <code>.route</code> call returns true, ONLY the action specified in the <code>.route</code> will be executed</p>
</blockquote>
<h3 id="route">route</h3>
<p>In this example, we short-circuit the promise chain if the request contains some indicator that this is just a test: </p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="c1">// import static ratpack.jackson.Jackson.fromJson
</span>
<span class="n">context</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">fromJson</span><span class="o">(</span><span class="n">Map</span><span class="o">))</span> <span class="n">route</span> <span class="o">({</span> <span class="n">Map</span> <span class="n">requestBody</span> <span class="o">-></span>
<span class="n">requestBody</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="s1">'test'</span><span class="o">)</span>
<span class="o">},</span> <span class="o">{</span> <span class="n">Map</span> <span class="n">requestBody</span> <span class="o">-></span>
<span class="c1">// If the predicate (1st param given to route) returns true, we execute this code
</span> <span class="c1">// but not the rest of the Promise chain
</span> <span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"This request was just a test"</span><span class="o">)</span>
<span class="o">})</span> <span class="n">map</span> <span class="o">{</span> <span class="n">Map</span> <span class="n">tableDetails</span> <span class="o">-></span>
<span class="c1">// This will not run if the requestBody contains a 'test' map key
</span> <span class="n">buildCreateTableRequest</span><span class="o">(</span><span class="n">tableDetails</span><span class="o">)</span>
<span class="o">}</span> <span class="n">flatMap</span> <span class="o">{</span> <span class="n">CreateTableRequest</span> <span class="n">tableRequest</span> <span class="o">-></span>
<span class="c1">// This will not run if the requestBody contains a 'test' map key
</span> <span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">dynamoDbClient</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">createTableRequest</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">CreateTableResult</span> <span class="n">result</span> <span class="o">-></span>
<span class="c1">// This will not run if the requestBody contains a 'test' map key
</span> <span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="s2">"The table ARN is ${result.tableDescription.tableArn}"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="onnull">onNull</h3>
<p>I've never used this but it looked interesting, so I thought I'd mention it here. If the Promised value is null, then this
action will be taken:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">Blocking</span><span class="o">.</span><span class="na">get</span> <span class="o">{</span>
<span class="n">externalCache</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">queryParams</span><span class="o">[</span><span class="s1">'id'</span><span class="o">])</span>
<span class="o">}</span> <span class="n">onNull</span> <span class="o">{</span>
<span class="n">context</span><span class="o">.</span><span class="na">clientError</span><span class="o">(</span><span class="n">HttpStatus</span><span class="o">.</span><span class="na">SC_NOT_FOUND</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span> <span class="n">String</span> <span class="n">cachedValue</span> <span class="o">-></span>
<span class="c1">// This won't be executed if a promise of null was returned from the cache
</span> <span class="n">context</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">cachedValue</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>Hopefully some examples, along with documentation links, help understand how the Promises work in Ratpack and how powerful
the Promise chain can be!</p>
Custom Content in Forms for Confluence Connect2016-11-03T00:00:00+00:00http://labs.adaptavist.com/confluence/2016/11/03/custom-content-in-forms-for-confluence-connect<h2 id="introduction">Introduction</h2>
<p>Confluence provides creation and collaboration features for different types of content like Spaces, Pages, Attachments, Comments, etc... and all these contents are supported by default within Confluence.
It is also possible to create new customised types of content that integrate with Confluence</p>
<p>Just like with the default content types the custom content types behave the same and they integrate tightly with Confluence features such as Search and Navigation, in addition to having the API capabilities of default Confluence content.</p>
<p>For the purpose of explaining custom content entities I'm going to show the implementation we use in Forms for Confluence Connect plugin. In this post I'm not giving much attention on UI elements, but it is more a discussion about how to configure and use custom content entities.</p>
<h2 id="configuration">Configuration</h2>
<p>To declare custom content we need to add the module to our atlassian-connect.json plugin descriptor. The example below only shows the main parts to configure as to stay in scope of this discussion.</p>
<div class="highlight"><pre><code class="language-" data-lang=""> "modules": {
"customContent": [
{
"key": "formconfig",
"name": {
"value": "Form Configurations"
},
"uiSupport": {
...
},
"apiSupport": {
"supportedContainerTypes": [
"space"
],
"supportedChildTypes": [
"ac:com.adaptavist.confluence.formMailNG:formresponse"
]
}
},
{
"key": "formresponse",
"name": {
"value": "Form Responses"
},
"uiSupport": {
...
},
"apiSupport": {
"supportedContainerTypes": [
"ac:com.adaptavist.confluence.formMailNG:formconfig"
],
"supportedChildTypes": [],
"indexing": {
"enabled": false
}
}
}
],
...
}
</code></pre></div>
<p>We started defining two different custom contents: 'Form Configurations' and 'Form Responses'. They both have a unique key and both define a parent-child relationship.</p>
<p>Form Configurations are children of a Space and contain Form Responses.</p>
<p>Form Responses can be contained within a Form Configuration, and that a Form Response is indeed a supported child type of a Form Configuration.</p>
<blockquote>
<p>The content type key for custom content is defined in 3 parts:</p>
<ul>
<li><p>ac:
This is always the same, indicating that this content type is defined in a Connect add-on.</p></li>
<li><p>addon-key:
The key of the Connect add-on</p></li>
<li><p>custom-content:
The key of the module which defines the custom content</p></li>
</ul>
</blockquote>
<h2 id="crud-actions">CRUD actions</h2>
<p>Because custom content is directly organised and maintained by Confluence, it can be created, retrieved, updated or deleted using the Confluence REST API.
I think that is the most powerful feature of the custom content; the API basically permit us to throw away the database layer and we don't have to spend
time to maintain all the different DBMS the clients have.</p>
<p>Following I'm going to show some examples of how the custom content entities are used within Forms for Confluence Connect plugin. The examples will focus on the request itself, nothing
is specified on how to handle the response given back from Confluence.</p>
<h4 id="create">Create</h4>
<p>Here's a POST request that is going to store new data in Confluence. In particular we're going to save a Form Configuration with:</p>
<ul>
<li>title</li>
<li>description</li>
<li>space</li>
<li>version</li>
<li>type</li>
</ul>
<p>The type is going to specify to Confluence that the new entity is our customized content type.</p>
<div class="highlight"><pre><code class="language-" data-lang="">var newFormConfigTitle = AJS.$('#formId').val();
AP.require('request', function (request) {
request({
url: '/rest/api/content',
type: 'POST',
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
version: {
number: 1
},
title: newFormConfigTitle,
type: "ac:com.adaptavist.confluence.formMailNG:formconfig",
space: {
key: getUrlParam('space_key')
},
body: {
storage: {
value: AJS.$('#formDescription').val(),
representation: "storage"
}
}
})
});
});
</code></pre></div>
<h4 id="get">Get</h4>
<p>To get all the Form Configurations children of a space, here is the simple request.</p>
<div class="highlight"><pre><code class="language-" data-lang="">AP.require('request', function (request) {
request({
url: '/rest/api/content?type=ac:com.adaptavist.confluence.formMailNG:formconfig' +
'&spaceKey=' + getUrlParam('space_key'),
type: 'GET',
contentType: "application/json; charset=utf-8"
});
});
</code></pre></div>
<h4 id="delete">Delete</h4>
<p>And finally how to delete a Form Configuration.</p>
<div class="highlight"><pre><code class="language-" data-lang="">AP.require('request', function (request) {
request({
url: '/rest/api/content/' + formConfigId,
type: 'DELETE',
contentType: 'application/json'
});
});
</code></pre></div>
<p>As we have seen the actions to interact with a custom content entity are very simple.
More details related to Confluence API can be found at <a href="https://docs.atlassian.com/confluence/REST/latest/">Confluence Cloud REST API Reference</a>.</p>
<h2 id="index">Index</h2>
<p>Custom content types are indexed as a built-in content type and rendered in quick search and site-wide search. The default value is true, so your custom content type will automatically be indexed if
you don't specify a value.</p>
<p>In the following example when we search for the string 'bar' we receive back a list of Form Configurations that match the search condition.</p>
<p><img src="/images/custom-content-search.png" alt=""></p>
<h2 id="conclusion">Conclusion</h2>
<p>As we have seen custom content well and truly provides a fully-integrated content solution within Confluence. The most interesting benefit I found so far is
that we completely dropped the database layer in our architecture. This means there is no time spent to configure a database connection or to fix compatibility problems with one dbms compared to another one.
A possible drawback can be that a normal database connection is more flexible and permits easier migrations. I cannot compare the two different solutions now because I didn't need to perfom any migration so far.</p>
Checking user permissions from REST calls2016-10-04T00:00:00+01:00http://labs.adaptavist.com/confluence/2016/10/04/checking-user-permissions-from-rest-calls<h2 id="introduction">Introduction</h2>
<p>This blog will show how to check the permissions of users making REST calls by using annotation. These changes
were made to our
<a href="https://marketplace.atlassian.com/plugins/com.adaptavist.confluence.formMailNG/server/overview">Forms for Confluence</a>
plugin.</p>
<h2 id="problem">Problem</h2>
<p>In the past we were only concerned with checking that the user making the call was indeed a global admin. Previously
only the global admin had access to the Forms for Confluence admin section. Recently we implemented a new feature which
required us to change the way permissions are checked. This is because we now also have a space level admin section
which is relevant only to forms created in that space. This means that we have to first establish if the call is being
made from the global admin level or space level, and then checking the permissions of the user.</p>
<h2 id="solution">Solution</h2>
<p>This is part of the original rest endpoint:</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"/emails/{id}"</span><span class="o">)</span>
<span class="kd">public</span> <span class="n">Response</span> <span class="nf">getEmail</span><span class="p">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="n">Long</span> <span class="n">id</span><span class="o">,</span>
<span class="nd">@QueryParam</span><span class="o">(</span><span class="s">"fullyLoaded"</span><span class="o">)</span> <span class="nd">@DefaultValue</span><span class="o">(</span><span class="s">"true"</span><span class="o">)</span> <span class="kt">boolean</span> <span class="n">fullyLoaded</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">safeCallAsAdmin</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">fullyLoaded</span><span class="o">,</span> <span class="k">new</span> <span class="n">RestCallerTwo</span><span class="o"><</span><span class="n">Long</span><span class="o">,</span> <span class="n">Boolean</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">Response</span> <span class="n">call</span><span class="o">(</span><span class="n">Long</span> <span class="n">id</span><span class="o">,</span> <span class="n">Boolean</span> <span class="n">fullyLoaded</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">entityResponse</span><span class="o">(</span><span class="n">inboxManager</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">fullyLoaded</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div>
<p>We took the opportunity to delegate the responsibility of authentication away from the receiver, because this would
improve code legibility, reuse and maintenance.</p>
<p>What we ended up with is the following:</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@GET</span>
<span class="nd">@Path</span><span class="o">(</span><span class="s">"/emails/{id}"</span><span class="o">)</span>
<span class="nd">@ResourceFilters</span><span class="o">(</span><span class="n">SpaceOrConfluenceAdminResourceFilter</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="n">Response</span> <span class="nf">getEmail</span><span class="p">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="n">Long</span> <span class="n">id</span><span class="o">,</span>
<span class="nd">@QueryParam</span><span class="o">(</span><span class="s">"fullyLoaded"</span><span class="o">)</span> <span class="nd">@DefaultValue</span><span class="o">(</span><span class="s">"true"</span><span class="o">)</span> <span class="kt">boolean</span> <span class="n">fullyLoaded</span><span class="o">,</span>
<span class="nd">@QueryParam</span><span class="o">(</span><span class="s">"spaceKey"</span><span class="o">)</span> <span class="n">String</span> <span class="n">spaceKey</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">entityResponse</span><span class="o">(</span><span class="n">inboxManager</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">fullyLoaded</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div>
<p>Notice the annotation @ResourceFilters. The argument is the class we created which will be responsible for checking
permissions.</p>
<p>Here is a snippet of the SpaceOrConfluenceAdminResourceFilter class:</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">ContainerRequest</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ContainerRequest</span> <span class="n">containerRequest</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">spaceKey</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">containerRequest</span><span class="o">.</span><span class="na">getQueryParameters</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">SPACE_KEY</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">spaceKey</span> <span class="o">=</span> <span class="n">containerRequest</span><span class="o">.</span><span class="na">getQueryParameters</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">SPACE_KEY</span><span class="o">).</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isAdminOrSpaceAdmin</span><span class="o">(</span><span class="n">spaceKey</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">containerRequest</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="n">AuthenticationRequiredException</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="kt">boolean</span> <span class="nf">isAdminOrSpaceAdmin</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">spaceKey</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">spaceKey</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">permissionManager</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="n">AuthenticatedUserThreadLocal</span><span class="o">.</span><span class="na">get</span><span class="o">(),</span>
<span class="n">Permission</span><span class="o">.</span><span class="na">ADMINISTER</span><span class="o">,</span> <span class="n">PermissionManager</span><span class="o">.</span><span class="na">TARGET_SYSTEM</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">spacePermissionManager</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="n">SpacePermission</span><span class="o">.</span><span class="na">ADMINISTER_SPACE_PERMISSION</span><span class="o">,</span>
<span class="n">spaceManager</span><span class="o">.</span><span class="na">getSpace</span><span class="o">(</span><span class="n">spaceKey</span><span class="o">),</span> <span class="n">AuthenticatedUserThreadLocal</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>We can determine if the user making the originating call is in a space by using the following javascript, and then
adding it to the rest call as a QueryParam.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">spaceKey</span> <span class="o">=</span> <span class="nx">AJS</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">spaceKey</span><span class="p">;</span>
<span class="nx">$http</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="nx">AJS</span><span class="p">.</span><span class="nx">contextPath</span><span class="p">()</span> <span class="o">+</span> <span class="nx">path</span> <span class="o">+</span> <span class="s1">'?'</span> <span class="o">+</span> <span class="s2">"spaceKey="</span> <span class="o">+</span> <span class="nx">spaceKey</span><span class="p">,</span> <span class="nx">postData</span><span class="p">);</span>
</code></pre></div>
<p>If the query parameter passed to the rest endpoint is empty, that means that the call is being made from the global
admin section of our pluging. This is because the global administration section of Confluence is not contained within
any space. This means that we know we have to check if the user has global permissions.</p>
<p>If the query parameter passed is a space, then we check the space permission privileges for a space
administrator. Since a global admin will have space admin privileges for all spaces we do not distinguish global admin
from space admin in this instance. However we do need to consider when it is a space admin, if this specific space admin
does have space admin privileges for the space in question.</p>
<p>Worth mentioning here is whether to use PathParam or QueryParam to send the space key. I learned it is better
to use QueryParam if the parameter is only sent in certain situations. PathParam is useful in situations where the
parameter is always sent. In this case the parameter is only sent if the call originates from a space, so a QueryParam
is appropriate.</p>
<h2 id="conclusion">Conclusion</h2>
<p>By using the ResourceFilters annotation we have simplified the REST endpoint and made the code easier to understand and
modify, by abstracting away the permission checking of the caller. Any new REST endpoint added simply needs to add one
line for the user permissions to be checked.</p>
Using the reflection API in Confluence2016-09-30T00:00:00+01:00http://labs.adaptavist.com/confluence/2016/09/30/using-reflection-in-confluence<h2 id="introduction">Introduction</h2>
<p>I recently had my first opportunity to use the Java Reflection API to solve a problem.</p>
<p>The following sections will provide a problem overview, how the problem was solved using reflection, and the result.</p>
<h2 id="the-problem">The Problem</h2>
<p>Our Content Formatting for Confluence plugin uses macros to define tables. These include the following tags:</p>
<ul>
<li>table</li>
<li>thead</li>
<li>th</li>
<li>tr</li>
<li>td</li>
<li>tbody</li>
</ul>
<p>Confluence now has its own table implementation in the editor, but the Content Formatting tables still provide some
added functionality.</p>
<p>From Confluence v5.9 Atlasssian introduced a macro wrapper that breaks macro generated html that is not valid.
Unfortunately it also introduced a bug.</p>
<p>Consider the following example:</p>
<div class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><table></span>
<span class="nt"><tr></span>
<span class="nt"><th></span>
<span class="nt"><p></span>text<span class="nt"></p></span>
<span class="nt"></th></span>
<span class="nt"><th></span>
<span class="nt"><p></span>text<span class="nt"></p></span>
<span class="nt"></th></span>
<span class="nt"></tr></span>
<span class="nt"><table></span>
</code></pre></div>
<p>Above we have a perfectly valid table, however this does not work. To be specific, the th tags were being stripped
from the table causing the table to render like this:</p>
<div class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><p></span>text<span class="nt"></p></span>
<span class="nt"><p></span>text<span class="nt"></p></span>
<span class="nt"><table></span>
<span class="nt"><tr></span>
<span class="nt"></tr></span>
<span class="nt"><table></span>
</code></pre></div>
<p>After some digging it became clear why this was happening.</p>
<p>The following is a snippet from the Confluence class ViewMacroMarshaller</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ViewMacroMarshaller</span> <span class="kd">implements</span> <span class="n">Marshaller</span><span class="o"><</span><span class="n">MacroDefinition</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">Logger</span> <span class="n">log</span> <span class="o">=</span> <span class="n">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="n">ViewMacroMarshaller</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">DEFAULT_MACRO_PLACEHOLDER</span> <span class="o">=</span> <span class="s">"<div class='default-macro-spinner spinner'>\n</div>"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">WRAP_MACRO_KEY</span> <span class="o">=</span> <span class="s">"confluence.wrap.macro"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">DISABLE_WRAP_MACRO_KEY</span> <span class="o">=</span> <span class="s">"confluence.wrap.macro.disable"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">Set</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">EXCLUDED_MACROS</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HashSet</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="n">add</span><span class="o">(</span><span class="s">"html"</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="s">"html-xhtml"</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="s">"td"</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="s">"tr"</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="s">"thead"</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="s">"tbody"</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="s">"tfoot"</span><span class="o">);</span>
<span class="o">};</span>
</code></pre></div>
<p>Here we can see that some tags are allowed, but mysteriously the th tag is not present in the Set "EXCLUDE_MACROS".
The next bit of code is the condition which uses the EXCLUDE_MACROS Set.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="k">try</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">wrapped</span> <span class="o">=</span> <span class="n">darkFeaturesManager</span><span class="o">.</span><span class="na">getDarkFeatures</span><span class="o">().</span><span class="na">isFeatureEnabled</span><span class="o">(</span><span class="n">WRAP_MACRO_KEY</span><span class="o">)</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">darkFeaturesManager</span><span class="o">.</span><span class="na">getDarkFeatures</span><span class="o">().</span><span class="na">isFeatureEnabled</span><span class="o">(</span><span class="n">DISABLE_WRAP_MACRO_KEY</span><span class="o">)</span>
<span class="o">&&</span> <span class="n">RenderContext</span><span class="o">.</span><span class="na">DISPLAY</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getOutputType</span><span class="o">())</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">EXCLUDED_MACROS</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">macroDefinition</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
</code></pre></div>
<h2 id="the-solution">The Solution</h2>
<p>At first I was sure it could not be fixed, however a colleague recommended using reflection.</p>
<p>I have not used reflection before so it was a bit of a learning curve. Thankfully in the end it was not hard to achieve.</p>
<p>First there are a few basic things to understand about the Reflection API. It really just means that we are inspecting
and changing a class at runtime. This is only recommended as a last resort because it is much slower than direct code.
It can add complexities and create maintenance issues. It also violates Java access modifier constraints.</p>
<p>The solution is to get the ViewMacroMarshaller class at run time, and then retrieve the Set containing the tags to
be excluded. If the Set does not contain the th tag, then we add it.</p>
<p>First we specify a component in the atlassian-plugin.xml file. This points to the class that will be responsible
for the modification.</p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><component</span> <span class="na">key=</span><span class="s">"modifyViewMacroMarshaller"</span> <span class="na">name=</span><span class="s">"Modify ViewMacroMarshaller"</span>
<span class="na">class=</span><span class="s">"com.adaptavist.confl....Initialization.ModifyViewMacroMarshaller"</span><span class="nt">></span>
<span class="nt"><description></span>Using reflection to modify a Confluence class to add the "th" tag,
workaround for a bug
<span class="nt"></description></span>
<span class="nt"><interface></span>org.springframework.beans.factory.InitializingBean<span class="nt"></interface></span>
<span class="nt"></component></span>
</code></pre></div>
<p>Then we create the class and implement the InitializingBean. We then implement the methods imposed by the interface,
and write a few lines of code. Some consideration needs to be taken where we encounter an earlier or later version of
the ViewMacroMarshaller class. This is because our plugin can be run with earlier or later versions of Confluence.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="n">afterPropertiesSet</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">Field</span> <span class="n">field</span> <span class="o">=</span> <span class="n">ViewMacroMarshaller</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredField</span><span class="o">(</span><span class="n">EXCLUDED_MACROS</span><span class="o">);</span>
<span class="n">field</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">field</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="kc">null</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!((</span><span class="n">Set</span><span class="o"><</span><span class="n">String</span><span class="o">>)</span> <span class="n">field</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="kc">null</span><span class="o">)).</span><span class="na">contains</span><span class="o">(</span><span class="s">"th"</span><span class="o">))</span> <span class="o">{</span>
<span class="o">((</span><span class="n">Set</span><span class="o"><</span><span class="n">String</span><span class="o">>)</span> <span class="n">field</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="kc">null</span><span class="o">)).</span><span class="na">add</span><span class="o">(</span><span class="s">"th"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">NoSuchFieldException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Unable to find EXCLUDE_MACROS field in ViewMacroMarshaller,"</span> <span class="o">+</span>
<span class="s">" this field does not exist in pre Confluence 5.9 "</span> <span class="o">+</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>Here we are getting the class and Set, and then performing some action on the Set if required. You might have noticed
something odd "field.get(null)". When the field we are trying to access is static then we must use null as the
argument.</p>
<h2 id="the-result">The Result</h2>
<p>The result is a properly formatted table which contains th elements.</p>
<p><img src="/images/table-containing-th.png" alt="My helpful screenshot"></p>
Creating a custom Confluence Blueprint2016-09-28T00:00:00+01:00http://labs.adaptavist.com/confluence/2016/09/28/creating-a-custom-confluence-blueprint<h2 id="introduction">Introduction</h2>
<p>Although Forms for Confluence is quite powerful in its functionality, it similarly has a bit of a learning curve for
new users trying to set it up. We were looking to simplify the setup process and get the user up and running in just a
few clicks. The solution we came up with was to use Confluence Blueprints. Blueprints is a user friendly way of making
use of templating.</p>
<p>This blog will show a few key steps that need to be taken to get a Confluence blueprint up and running. Examples will
be used from our implementation.</p>
<h2 id="specifying-resources">Specifying resources</h2>
<p>We need to specify a few resources in the atlassian-plugin.xml file associated with the plugin.</p>
<h4 id="content-template">Content template</h4>
<p>Here we specify the class that is responsible for some back-end processing, which responds to the user selection by
returning the updated BlueprintContext object containing the relevant rendered template.</p>
<p>Note: Some i18n naming has been removed and some lines of code have been moved to improve
readability within the constraints of the blog size. </p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><content-template</span> <span class="na">key=</span><span class="s">"basic-form-template"</span> <span class="na">template-title-key=</span><span class="s">"Basic Blueprint"</span><span class="nt">></span>
<span class="nt"><resource</span> <span class="na">name=</span><span class="s">"template"</span> <span class="na">type=</span><span class="s">"download"</span>
<span class="na">location=</span><span class="s">"templates/forms/blueprints/blueprint-template.xml"</span><span class="nt">/></span>

<span class="nt"><context-provider</span>
<span class="na">class=</span><span class="s">"com.adaptavist.confluence.forms.view.blueprint.BlueprintContextProvider"</span><span class="nt">/></span>

<span class="nt"></content-template></span>
</code></pre></div>
<h4 id="blueprint">Blueprint</h4>
<p>Specifies the blueprint and binds it to the Confluence Create dialog. We include the dialog-wizard tag because we would
like to use our own wizard.</p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><blueprint</span> <span class="na">key=</span><span class="s">"forms-blueprint"</span> <span class="na">content-template-key=</span><span class="s">"basic-form-template"</span> <span class="na">index-key=</span><span class="s">"forms"</span><span class="nt">></span>

<span class="nt"><dialog-wizard</span> <span class="na">key=</span><span class="s">"formsblueprint-wizard"</span><span class="nt">></span>         

<span class="nt"><dialog-page</span> <span class="na">id=</span><span class="s">"blueprintSelectionWizardPage1"</span><span class="err">
</span>
<span class="na">template-key=</span><span class="s">"Forms.Blueprints.blueprintSelectionWizardPage1"</span><span class="err">
</span>
<span class="na">title-key=</span><span class="s">"Selection"</span><span class="nt">/></span>


<span class="nt"><dialog-page</span> <span class="na">id=</span><span class="s">"inputTitleWizardPage2"</span>
<span class="na">template-key=</span><span class="s">"Forms.Blueprints.inputTitleWizardPage2"</span><span class="err">
</span>
<span class="na">title-key=</span><span class="s">"Configuration"</span><span class="nt">/></span>

<span class="nt"></dialog-wizard></span>

<span class="nt"></blueprint></span>
</code></pre></div>
<h4 id="web-item">Web-item</h4>
<p>This entry is responsible for creating a clickable link/icon inside the Confluence Create dialog. We also include the
condition tag because we only want to show the blueprint option under certain conditions, more on this later.</p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><web-item</span> <span class="na">key=</span><span class="s">"create-forms-blueprint"</span> <span class="na">name=</span><span class="s">"Forms for Confluence"</span>
<span class="na">section=</span><span class="s">"system.create.dialog/content"</span><span class="nt">></span>
<span class="nt"><description</span> <span class="na">key=</span><span class="s">"Create a pre-configured form"</span><span class="nt">/></span>
<span class="nt"><resource</span> <span class="na">name=</span><span class="s">"icon"</span> <span class="na">type=</span><span class="s">"download"</span>
<span class="na">location=</span><span class="s">"resources/images/blueprint/blueprint-icon-generic.png"</span><span class="nt">/></span>
<span class="nt"><param</span> <span class="na">name=</span><span class="s">"blueprintKey"</span> <span class="na">value=</span><span class="s">"forms-blueprint"</span><span class="nt">/></span>
<span class="nt"><condition</span>
<span class="na">class=</span><span class="s">"com.adaptavist.confluence.forms.controller.conditions.BlueprintShowCondition"</span><span class="nt">></span>
<span class="nt"><param</span> <span class="na">name=</span><span class="s">"permission"</span><span class="nt">></span>admin<span class="nt"></param></span>
<span class="nt"></condition></span>
<span class="nt"></web-item></span>
</code></pre></div>
<h4 id="web-resources">Web-resources</h4>
<p>Lastly we need to specify the resources we need for the wizard. This will include the .soy templates for each page of
the wizard, javascript file and CSS file.</p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><web-resource</span> <span class="na">key=</span><span class="s">"form-wizard-resources"</span> <span class="na">name=</span><span class="s">"forms wizard Web Resources"</span><span class="nt">></span>    

<span class="nt"><dependency></span>confluence.web.resources:ajs<span class="nt"></dependency></span>

<span class="nt"><transformation</span> <span class="na">extension=</span><span class="s">"js"</span><span class="nt">></span>

<span class="nt"><transformer</span> <span class="na">key=</span><span class="s">"jsI18n"</span><span class="nt">/></span>

<span class="nt"></transformation></span>

<span class="nt"><transformation</span> <span class="na">extension=</span><span class="s">"soy"</span><span class="nt">></span>

<span class="nt"><transformer</span> <span class="na">key=</span><span class="s">"soyTransformer"</span><span class="nt">></span>

<span class="nt"><functions></span>com.atlassian.confluence.plugins.soy:soy-core-functions<span class="nt"></functions></span>

<span class="nt"></transformer></span>

<span class="nt"></transformation></span>

<span class="nt"><resource</span> <span class="na">type=</span><span class="s">"download"</span> <span class="na">name=</span><span class="s">"selectFormBlueprintWizardPage1.soy.js"</span><span class="err">
</span>
<span class="na">location=</span><span class="s">"templates/forms/blueprints/selectFormBlueprintWizardPage1.soy"</span><span class="nt">/></span>

<span class="nt"><resource</span> <span class="na">type=</span><span class="s">"download"</span> <span class="na">name=</span><span class="s">"inputTitleWizardPage2.soy.js"</span><span class="err">
</span>
<span class="na">location=</span><span class="s">"templates/forms/blueprints/inputTitleWizardPage2.soy"</span><span class="nt">/></span>

<span class="nt"><resource</span> <span class="na">type=</span><span class="s">"download"</span> <span class="na">name=</span><span class="s">"wizardPages.js"</span> <span class="na">location=</span><span class="s">"js/forms/blueprint/wizardPages.js"</span><span class="nt">/></span>

<span class="nt"><resource</span> <span class="na">type=</span><span class="s">"download"</span> <span class="na">name=</span><span class="s">"blueprint.css"</span><span class="err">
</span> <span class="na">location=</span><span class="s">"css/blueprint/blueprint.css"</span><span class="nt">/></span>

<span class="nt"><resource</span> <span class="na">type=</span><span class="s">"download"</span> <span class="na">name=</span><span class="s">"blueprint-images/"</span> <span class="na">location=</span><span class="s">"resources/images/blueprint/"</span><span class="nt">/></span>

<span class="nt"><context></span>formsblueprint<span class="nt"></context></span>

<span class="nt"><context></span>atl.general<span class="nt"></context></span>

<span class="nt"></web-resource></span>
</code></pre></div>
<h2 id="show-blueprint-condition">Show blueprint condition</h2>
<p>Because this blueprint allows the creation of forms there are some restrictions involved. We do not want to show the
blueprint option for users who do not have permission. So we need some mechanism for determining the users admin
privileges. Confluence provides a Condition interface which we implement in our class, which simply checks the users
permissions and then returns true or false. This in turn shows or hides the blueprint web item.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BlueprintShowCondition</span> <span class="kd">implements</span> <span class="n">Condition</span>
<span class="o">{</span><span class="err">

</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">SPACE</span> <span class="o">=</span> <span class="s">"space"</span><span class="o">;</span><span class="err">
</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">PermissionManager</span> <span class="n">permissionManager</span><span class="o">;</span><span class="err">
</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">SpacePermissionManager</span> <span class="n">spacePermissionManager</span><span class="o">;</span><span class="err">

</span>
<span class="kd">private</span> <span class="n">BlueprintShowCondition</span><span class="o">(</span><span class="n">PermissionManager</span> <span class="n">permissionManager</span><span class="o">,</span>
<span class="n">SpacePermissionManager</span> <span class="n">spacePermissionManager</span><span class="o">)</span> <span class="o">{</span><span class="err">
</span>
<span class="k">this</span><span class="o">.</span><span class="na">permissionManager</span> <span class="o">=</span> <span class="n">permissionManager</span><span class="o">;</span><span class="err">
</span>
<span class="k">this</span><span class="o">.</span><span class="na">spacePermissionManager</span> <span class="o">=</span> <span class="n">spacePermissionManager</span><span class="o">;</span><span class="err">
</span>
<span class="o">}</span><span class="err">

</span>
<span class="nd">@Override</span><span class="err">
</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="n">shouldDisplay</span><span class="o">(</span><span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">Object</span><span class="o">></span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span><span class="err">
</span>
<span class="n">ConfluenceUser</span> <span class="n">confluenceUser</span> <span class="o">=</span> <span class="o">(</span><span class="n">ConfluenceUser</span><span class="o">)</span> <span class="n">context</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"user"</span><span class="o">);</span><span class="err">

</span>
<span class="k">if</span> <span class="o">(</span><span class="n">confluenceUser</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span><span class="err">
</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">permissionManager</span><span class="o">.</span><span class="na">isConfluenceAdministrator</span><span class="o">(</span><span class="n">confluenceUser</span><span class="o">)</span> <span class="o">||</span><span class="err">
</span>
<span class="n">spacePermissionManager</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="n">SpacePermission</span><span class="o">.</span><span class="na">ADMINISTER_SPACE_PERMISSION</span><span class="o">,</span>
<span class="o">(</span><span class="n">Space</span><span class="o">)</span> <span class="n">context</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">SPACE</span><span class="o">),</span> <span class="n">confluenceUser</span><span class="o">))</span> <span class="o">{</span><span class="err">
</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span><span class="err">
</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="err">
</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span><span class="err">
</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="err">
</span><span class="o">}</span>
</code></pre></div>
<h2 id="wizard">Wizard</h2>
<p>The wizard is just a few dialog pages which the user can step through easily that provides us with some input with
regards to creating the template. In this case we use the first dialog page of the wizard to present the user with
several templates to choose from. On the second dialog page we require a page title and form ID. We also provide some
feedback and visual aid to the user with regards to the required input fields.</p>
<p>Good to know is that the wizard can be extended with more steps and a progress tracker for complex templates. This
however is outside of the scope of this writing. Have a look at the
<a href="https://docs.atlassian.com/aui/5.5.1/docs/progressTracker.html">Confluence AUI Tracker</a> for more info.</p>
<p>To render the dialog pages we use soy templates. The following is an example of the second dialog page. The rendered
pages can be seen at the bottom of this section.</p>
<div class="highlight"><pre><code class="language-html" data-lang="html">{namespace Forms.Blueprints}
/**
*Page 2: On this page the user specifies the page name and the unique form id to be created.
*/
{template .inputTitleWizardPage2}
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"aui-group"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"aui-item wizard-page-two-left"</span><span class="nt">></span>
<span class="nt"><form</span> <span class="na">name=</span><span class="s">"wizard-page-two"</span> <span class="na">action=</span><span class="s">"#"</span> <span class="na">method=</span><span class="s">"post"</span> <span class="na">class=</span><span class="s">"aui top-label"</span><span class="nt">></span>
<span class="nt"><fieldset></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"field-group"</span><span class="nt">></span>
<span class="nt"><label</span> <span class="na">id=</span><span class="s">"page-title-label"</span> <span class="na">for=</span><span class="s">"page-title"</span><span class="nt">></span>
Page Title
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"aui-icon icon-required"</span><span class="nt">></span>required<span class="nt"></span></span>
<span class="nt"></label></span>
<span class="nt"><input</span> <span class="na">class=</span><span class="s">"text"</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"page-title"</span> <span class="na">name=</span><span class="s">"title"</span> <span class="na">title=</span><span class="s">"title"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"error"</span> <span class="na">id=</span><span class="s">"page-title-error"</span><span class="nt">></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"description"</span><span class="nt">></span>
A title for the page that will hold your form
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"field-group"</span><span class="nt">></span>
<span class="nt"><label</span> <span class="na">id=</span><span class="s">"form-id-label"</span> <span class="na">for=</span><span class="s">"formId"</span><span class="nt">></span>
Form ID
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"aui-icon icon-required"</span><span class="nt">></span>required<span class="nt"></span></span>
<span class="nt"></label></span>
<span class="nt"><input</span> <span class="na">class=</span><span class="s">"text"</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"formId"</span> <span class="na">name=</span><span class="s">"formId"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"error"</span> <span class="na">id=</span><span class="s">"form-id-error"</span><span class="nt">></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"description"</span><span class="nt">></span>
A unique identifier for your form
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></fieldset></span>
<span class="nt"></form></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"aui-item"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"form-configuration-picture title"</span><span class="nt">></span>
<span class="ni">&nbsp;</span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
{/template}
</code></pre></div>
<p>We also need some javascript to handle validation and user input as the user steps through the wizard pages. Confluence
provides some structure on how to achieve this. Start by looking at the bottom on the javascript file for the function
that sets the wizard. For some specific user actions related to the wizard, we can specify a function that implements
some behaviour we want. Follow the link
<a href="https://developer.atlassian.com/confdev/confluence-plugin-guide/confluence-blueprints/javascript-api-for-blueprint-wizards">JS API for blueprint wizard</a>
for more info.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">AJS</span><span class="p">.</span><span class="nx">toInit</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">$</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">selectedBlueprintId</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">handleWizardPage1</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">selectedBlueprintId</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">$container</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'.selected'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">id</span><span class="p">;</span>
<span class="nx">state</span><span class="p">.</span><span class="nx">nextPageId</span> <span class="o">=</span> <span class="s1">'inputTitleWizardPage2'</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">handleWizardPage2</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">success</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="c1">//add the selected blueprint to the wizard data.
</span> <span class="nx">state</span><span class="p">.</span><span class="nx">pageData</span><span class="p">.</span><span class="nx">userSelectedBlueprint</span> <span class="o">=</span> <span class="nx">selectedBlueprintId</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">formId</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">pageData</span><span class="p">.</span><span class="nx">formId</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">pageTitle</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">pageData</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">formIdErrorField</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">$container</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'#form-id-error'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">pageTitleErrorField</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">$container</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'#page-title-error'</span><span class="p">);</span>
<span class="c1">//validate and highlight fields
</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">pageTitle</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">pageTitleErrorField</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">AJS</span><span class="p">.</span><span class="nx">I18n</span><span class="p">.</span><span class="nx">getText</span>
<span class="p">(</span><span class="s2">"com.adaptavist.confluence.forms.blueprint.validation.page.title"</span><span class="p">));</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#page-title'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
<span class="nx">success</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">formId</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">formIdErrorField</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">AJS</span><span class="p">.</span><span class="nx">I18n</span><span class="p">.</span><span class="nx">getText</span>
<span class="p">(</span><span class="s2">"com.adaptavist.confluence.forms.blueprint.validation.form.id"</span><span class="p">));</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#formId'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
<span class="nx">success</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">((</span><span class="o">!</span><span class="nx">pageTitle</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="o">!</span><span class="nx">formId</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#page-title'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">success</span> <span class="o">===</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">success</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="s1">'GET'</span><span class="p">,</span>
<span class="na">async</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">url</span><span class="p">:</span> <span class="nx">Confluence</span><span class="p">.</span><span class="nx">getContextPath</span><span class="p">()</span> <span class="o">+</span> <span class="s1">'/rest/formservice/1.0/configs/formmail/name/'</span>
<span class="o">+</span> <span class="nx">formId</span> <span class="o">+</span> <span class="s1">'?'</span> <span class="o">+</span> <span class="s2">"spaceKey="</span> <span class="o">+</span> <span class="nx">AJS</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">spaceKey</span><span class="p">,</span>
<span class="na">dataType</span><span class="p">:</span> <span class="s1">'json'</span>
<span class="p">}).</span><span class="nx">success</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">json</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">formIdErrorField</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">AJS</span><span class="p">.</span><span class="nx">I18n</span><span class="p">.</span><span class="nx">getText</span>
<span class="p">(</span><span class="s2">"com.adaptavist.confluence.forms.blueprint.validation.form.id.exist.error"</span><span class="p">));</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#formId'</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
<span class="nx">success</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}).</span><span class="nx">fail</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">json</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">success</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">success</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">handleWizardPage1Input</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">expensesBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-expenses'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">jobAppBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-job-application'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">trainingBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-training'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">employeeSurveyBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-employee-satisfaction-survey'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">incidentBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-incident-report'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">contactBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-contact-form'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">genericBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-generic-form'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">eventSurveyBlueprint</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#blueprint-event-survey'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">blueprints</span> <span class="o">=</span> <span class="p">[</span><span class="nx">expensesBlueprint</span><span class="p">,</span> <span class="nx">jobAppBlueprint</span><span class="p">,</span> <span class="nx">trainingBlueprint</span><span class="p">,</span>
<span class="nx">employeeSurveyBlueprint</span><span class="p">,</span> <span class="nx">incidentBlueprint</span><span class="p">,</span> <span class="nx">contactBlueprint</span><span class="p">,</span> <span class="nx">genericBlueprint</span><span class="p">,</span>
<span class="nx">eventSurveyBlueprint</span><span class="p">];</span>
<span class="nx">genericBlueprint</span><span class="p">.</span><span class="nx">addClass</span><span class="p">(</span><span class="s1">'selected'</span><span class="p">);</span>
<span class="c1">//on each user blueprint selection unselect other bluerprints and select the current one
</span> <span class="nx">blueprints</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">blueprint</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">blueprint</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">unselectOtherBlueprints</span><span class="p">(</span><span class="nx">blueprints</span><span class="p">,</span> <span class="nx">blueprint</span><span class="p">);</span>
<span class="nx">blueprint</span><span class="p">.</span><span class="nx">addClass</span><span class="p">(</span><span class="s1">'selected'</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">handleWizardPage2Input</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">formIdInputField</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">$container</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'#formId'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">pageTitleInputField</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">$container</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'#page-title'</span><span class="p">);</span>
<span class="c1">//clear input field on change
</span> <span class="nx">formIdInputField</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'change'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#form-id-error'</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">pageTitleInputField</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'change'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#page-title-error'</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">unselectOtherBlueprints</span><span class="p">(</span><span class="nx">blueprints</span><span class="p">,</span> <span class="nx">blueprint</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">blueprintsAmount</span> <span class="o">=</span> <span class="nx">blueprints</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">blueprintsAmount</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nx">blueprints</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">===</span> <span class="nx">blueprint</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">blueprints</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">hasClass</span><span class="p">(</span><span class="s1">'selected'</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">blueprints</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">toggleClass</span><span class="p">(</span><span class="s1">'selected'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">Confluence</span><span class="p">.</span><span class="nx">Blueprint</span><span class="p">.</span><span class="nx">setWizard</span>
<span class="p">(</span><span class="s1">'com.adaptavist.confluence.formMailNG:create-forms-blueprint'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">wizard</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">wizard</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'submit.blueprintSelectionWizardPage1'</span><span class="p">,</span> <span class="nx">handleWizardPage1</span><span class="p">);</span>
<span class="nx">wizard</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'submit.inputTitleWizardPage2'</span><span class="p">,</span> <span class="nx">handleWizardPage2</span><span class="p">);</span>
<span class="nx">wizard</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'post-render.blueprintSelectionWizardPage1'</span><span class="p">,</span> <span class="nx">handleWizardPage1Input</span><span class="p">);</span>
<span class="nx">wizard</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'post-render.inputTitleWizardPage2'</span><span class="p">,</span> <span class="nx">handleWizardPage2Input</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div>
<h4 id="confluence-create-page-dialog">Confluence create page dialog</h4>
<p><img src="/images/confluence-create-dialog.png" alt="My helpful screenshot"></p>
<h4 id="blueprint-wizard-step-one">Blueprint wizard step one</h4>
<p><img src="/images/blueprint-wizard-page-one.png" alt="My helpful screenshot"></p>
<h4 id="blueprint-wizard-step-two">Blueprint wizard step two</h4>
<p><img src="/images/blueprint-wizard-page-two.png" alt="My helpful screenshot"></p>
<h2 id="context-provider">Context provider</h2>
<p>Once the user finishes with the wizard by clicking the create button on page two of the wizard, then we get a chance to
do some processing. We do this by extending the AbstractBlueprintContextProvider class and implementing the
updateBlueprintContext method in our newly created class.</p>
<p>At this point we create the form ID with the input from the user and provide the velocity template corresponding to the
users selection. This template will be updated with the title the user provided. The templates are saved in .vm files
which we retrieved and render as required. The rendered template is added to the BlueprintContextProvider object and
returned.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="n">BlueprintContext</span> <span class="n">updateBlueprintContext</span><span class="o">(</span><span class="n">BlueprintContext</span> <span class="n">blueprintContext</span><span class="o">)</span> <span class="o">{</span>
<span class="n">createFormId</span><span class="o">(</span><span class="n">blueprintContext</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">templatePath</span> <span class="o">=</span>
<span class="o">(</span><span class="n">getTemplateLocation</span><span class="o">((</span><span class="n">String</span><span class="o">)</span> <span class="n">blueprintContext</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">CONFIGURATION_OPTION</span><span class="o">)));</span>
<span class="n">VelocityContextBuilder</span> <span class="n">velocityContextBuilder</span> <span class="o">=</span> <span class="n">contextBuilder</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">key</span> <span class="o">:</span> <span class="n">blueprintContext</span><span class="o">.</span><span class="na">getMap</span><span class="o">().</span><span class="na">keySet</span><span class="o">())</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">Object</span> <span class="n">o</span> <span class="o">=</span> <span class="n">blueprintContext</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
<span class="n">velocityContextBuilder</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">o</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">context</span> <span class="o">=</span>
<span class="n">VelocityUtils</span><span class="o">.</span><span class="na">getRenderedTemplate</span><span class="o">(</span><span class="n">templatePath</span><span class="o">,</span> <span class="n">velocityContextBuilder</span><span class="o">.</span><span class="na">build</span><span class="o">());</span>
<span class="n">blueprintContext</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">CONTEXT</span><span class="o">,</span> <span class="n">context</span><span class="o">);</span>
<span class="k">return</span> <span class="n">blueprintContext</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div>
<p>Since we will have more than one template to choose from, we need to provide a blueprint-template.xml placeholder.</p>
<div class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><at:declarations></span>

<span class="nt"><at:var</span> <span class="na">at:name=</span><span class="s">'context'</span><span class="nt">/></span>

<span class="nt"></at:declarations></span>

<span class="nt"><at:var</span> <span class="na">at:name=</span><span class="s">'context'</span> <span class="na">at:rawxhtml=</span><span class="s">'true'</span><span class="nt">/></span>
</code></pre></div>
<h2 id="conclusion">Conclusion</h2>
<p>Making a feature or plugin appealing is a two edged sword. We want to provide complexity and features, yet at the same
time we need to balance it against ease of use. There is no silver bullet, but by using blueprints we can abstract away
some of the complexity for new users. This feature is not just for new users. Advanced users can also make use of
blueprints as this speeds up the creation of a form. The added bonus is that it even helps development by being able
to test faster. </p>
ReactJS in Forms for Confluence Connect2016-09-06T00:00:00+01:00http://labs.adaptavist.com/confluence/2016/09/06/react-js-in-forms-for-confluence-connect<h2 id="introduction">Introduction</h2>
<p>The initial purpose of the blog was to show how ReactJS is used inside the Forms for Confluence Connect plugin, but later it changed to mainly share how I approached the new (for me) library.
I think would be a good idea to write about my experience using React, the problems I found and provide some tips for people that are going to start using it. I'll try to summarize all things that I wish I'd known when I started out, or things that really helped me to use React.</p>
<h2 id="what-is-react">What is React?</h2>
<p>From the <a href="https://facebook.github.io/react/docs/why-react.html">React documentation</a> it is described as a JavaScript library for creating user interfaces. Many people choose to think of React as the V in MVC.
What I came to understand while working with it is that React is essentially a library to render HTML. That's all React outputs, HTML.
That means that while using React to organize your HTML, it's still possible to use other powerful tools like jQuery and ES6 that are used in the Forms for Confluence Connect plugin.</p>
<h2 id="thinking-in-react">Thinking in React</h2>
<p>My initial dumb suggestion to approach React is to read the first steps in the <a href="https://facebook.github.io/react/docs/getting-started.html">documentation</a>. The most useful thing I found is <a href="https://facebook.github.io/react/docs/thinking-in-react.html">Thinking in React</a>.
The main topic to wrap your head around React is to start thinking in components. As explained by the documentation the first step is to break the UI into a component hierarchy and to render each component separately.
Here's a very simple example of a table with two rows.</p>
<div class="highlight"><pre><code class="language-" data-lang="">
class Table extends React.Component {
render() {
return <div>
<h2>Table title</h2>
<table>
<thead>
<Header />
</thead>
<tbody>
<Row firstname="Jill" lastname="Smith" age="50"/>
<Row firstname="Eve" lastname="Jackson" age="94"/>
</tbody>
</table>
</div>
}
}
class Header extends React.Component {
render() {
return <tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Age</th>
</tr>
}
}
class Row extends React.Component {
render() {
return <tr>
<td>{this.props.firstname}</td>
<td>{this.props.lastname}</td>
<td>{this.props.age}</td>
</tr>
}
}
</code></pre></div>
<p>The example highlights that it is possible to mix React components with regular HTML elements in your render function.
It's good practice to define <a href="https://facebook.github.io/react/docs/multiple-components.html">simple, single responsibility components</a>.</p>
<blockquote>
<p>The render function in our component is used to display the HTML content, but you are only permitted to return one DOM element or React component. If you need to display more html elements, like in the case of Table component, they need to be wrapped by a div.</p>
</blockquote>
<h2 id="state-vs-props">State vs Props</h2>
<p>This is the heart of React. Each component has a state and properties. When those change, the render method is called with the new values and the UI is updated to the new output.
It is basically the step to pass from a static rendering engine to make React components dynamic.</p>
<p>The properties are read-only by their owning component. Any component can change its own state or even a different component's state if it wants to.
A state should change following a user action, such as a state value that keeps track of whether or not the user has clicked on a button.</p>
<p>I suggest that you don't use the state and the properties when they're not needed, and it's preferable to avoid them where possible. This means that is preferable to have "stateless component",
which is a component that will always do the same thing when given the same set of properties. Stateless components are easier to understand and easier to test, which is always a good thing.</p>
<div class="highlight"><pre><code class="language-" data-lang="">
class Table extends React.Component {
parentClickButtonFunction(event) {
this.setState({
tableTitle: "new title"
});
}
constructor() {
super();
this.state = {
tableTitle: "initial table title"
}
this.parentClickButtonFunction = this.parentClickButtonFunction.bind(this);
}
render() {
return <div>
<h2>{this.state.tableTitle}</h2>
<table>
<thead>
<Header />
</thead>
<tbody>
<Row firstname="Jill" lastname="Smith" age="50"/>
<Row firstname="Eve" lastname="Jackson" age="94"/>
</tbody>
</table>
<AddRowButton parentClickButtonFunction={this.parentClickButtonFunction}/>
</div>
}
}
class Header extends React.Component {
render() {
return <tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Age</th>
</tr>
}
}
class Row extends React.Component {
render() {
return <tr>
<td>{this.props.firstname}</td>
<td>{this.props.lastname}</td>
<td>{this.props.age}</td>
</tr>
}
}
class AddRowButton extends React.Component {
clickButtonFunction(event) {
this.props.parentClickButtonFunction();
}
render() {
return <button type="button" onClick={this.clickButtonFunction.bind(this)}>Click Me!
</button>
}
}
</code></pre></div>
<p>Now I would like to focus on how the state and properties work.
I advise that you give the responsibility of the state to the parent component and propagate the changes from the top to the bottom of the components hierarchy using the properties.</p>
<p>The button in the example above wants to change the state, but to do that, it actually propagates the call to its parent using the callback function 'parentClickButtonFunction' in its properties.</p>
<h2 id="es6-classes-and-main-methods">ES6 Classes and main methods</h2>
<p>In the Forms for Confluence Connect plugin we use <a href="https://facebook.github.io/react/docs/reusable-components.html#es6-classes">ES6 class syntax</a> to define components.
The important things to keep in mind are that any new component should extend 'React.Component' and that there are some important methods to be mindful of.</p>
<ul>
<li><h4 id="render">render:</h4>
<p>The render function in our component is used to display the HTML output of your component.</p></li>
<li><h4 id="constructor">constructor:</h4>
<p>The constructor is where you initialize the component state and properties</p></li>
<li><h4 id="componentdidmount">componentDidMount:</h4>
<p>Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
At this point in the lifecycle, you can access any refs to your children (e.g., to access the underlying DOM representation).
The componentDidMount() method of child components is invoked before that of parent components.</p></li>
</ul>
<h2 id="no-autobinding">No Autobinding</h2>
<p>As reported in the <a href="https://facebook.github.io/react/docs/reusable-components.html#no-autobinding">documentation</a>, ES6 classes don't automatically bind this to the instance and it's recommended
to bind the event handlers in the constructor so that they are only bound once for every instance. That explains the previous Table constructor:</p>
<div class="highlight"><pre><code class="language-" data-lang="">constructor() {
super();
this.state = {
tableTitle: "initial table title"
}
this.parentClickButtonFunction = this.parentClickButtonFunction.bind(this);
}
</code></pre></div>
<h2 id="conclusion">Conclusion</h2>
<p>Thinking in React is thinking in a different way compared to how I, and probably the majority of people, were used to. It can be difficult at the beginning to understanding how properties, state, and component communication work because it is not straightforward.
The good part I found is that you can always tell how your component will render by looking at one source file. This may be the most important benefit.
If you know the state, you know the rendered output. You don't have to trace program flow. When working on complex applications, especially in teams, this is critically important.</p>
Migrating to ES6 in Atlassian Add-ons2016-04-25T00:00:00+01:00http://labs.adaptavist.com/code/2016/04/25/migrating-to-es6-in-atlassian-add-ons<h2 id="es6-is-the-new-dhtml">ES6 is the new DHTML</h2>
<p>Anyone remember DHTML? I remember it being awesome. Suddenly we were able to do all kinds of exciting things like <a href="http://www.mf2fm.com/rv/dhtmlclicksplosion.php">displaying fireworks when our mouse is clicked</a>, creating our own <a href="http://web.archive.org/web/20051213012655/http://www.dhtmlgoodies.com/scripts/tooltip_shadow/tooltip_shadow.html">tooltips with funky styling</a> and <a href="http://web.archive.org/web/19980130120628/http://htmlgoodies.com/javagoodies/st.html">scrolling stuff automatically</a>. And all in the global scope! Those were the good old days ☺ </p>
<p>Now that <a href="https://kangax.github.io/compat-table/es6/">a lot of ES6 is supported</a> either by browsers themselves, or <a href="https://babeljs.io/">Babel</a>/<a href="https://github.com/google/traceur-compiler">Traceur</a>, we at <a href="http://www.adaptavist.com/">Adaptavist</a> are eager to embrace the cool new features that the ES6 standard provides, which are somewhat more nerdy than the examples above, but no less exciting!</p>
<ul>
<li><a href="http://exploringjs.com/es6/ch_modules.html">Modules</a></li>
<li><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">Object and array destructuring</a></li>
<li><a href="http://www.2ality.com/2015/02/es6-scoping.html">Constants</a></li>
<li><a href="https://strongloop.com/strongblog/an-introduction-to-javascript-es6-arrow-functions/">Arrow functions</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">String interpolation</a></li>
<li><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator">Spread operator</a></li>
<li><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/default_parameters">Default parameter values</a></li>
</ul>
<h2 id="our-es6-stack">Our ES6 stack</h2>
<p>At the moment our typical technology stack for including ES6 in an Atlassian add-on looks a bit like this: the <a href="https://github.com/eirslett/frontend-maven-plugin">frontend-maven-plugin</a> allows us to download/install <a href="https://nodejs.org">Node</a> and <a href="https://www.npmjs.com/package/npm">NPM</a> and run <a href="http://gruntjs.com/">Grunt</a> to compile/convert/batch our ES6 files into JS using Babel, <a href="https://github.com/mishoo/UglifyJS2">Uglify</a> and any other dependencies we need. We're currently using <a href="https://karma-runner.github.io/0.13/index.html">Karma</a> to run unit tests. </p>
<p>Both JIRA and Confluence use <a href="https://github.com/requirejs/almond">almond.js</a> as their JS module loader, so we use the following Babel config (or something similar) in our Gruntfile.js to turn our ES6 files into well named JS modules.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">grunt</span><span class="p">.</span><span class="nx">initConfig</span><span class="p">({</span>
<span class="c1">// Convert ES6 to ES5
</span> <span class="nl">babel</span><span class="p">:</span> <span class="p">{</span>
<span class="na">options</span><span class="p">:</span> <span class="p">{</span>
<span class="na">sourceMap</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// or inline
</span> <span class="na">moduleIds</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">presets</span><span class="p">:</span> <span class="p">[</span><span class="s2">"es2015"</span><span class="p">],</span>
<span class="na">getModuleId</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">name</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^.*src\/main\/frontend\/web-resource\/(.+)$/</span><span class="p">,</span> <span class="s1">'$1'</span><span class="p">)</span>
<span class="p">},</span>
<span class="c1">// If I remember right, this is mainly to allow us to put a "/" at the start of
</span> <span class="c1">// module names so our IDE resolves the module names to the correct files
</span> <span class="na">resolveModuleSource</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">source</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">source</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^\//</span><span class="p">,</span> <span class="s2">""</span><span class="p">);</span> <span class="c1">// Strip the leading / from module names
</span> <span class="p">}</span>
<span class="p">},</span>
<span class="na">main</span><span class="p">:</span> <span class="p">{</span>
<span class="na">options</span><span class="p">:</span> <span class="p">{</span>
<span class="na">sourceRoot</span><span class="p">:</span> <span class="s1">'src/main/frontend/web-resource'</span>
<span class="p">},</span>
<span class="na">files</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">expand</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">cwd</span><span class="p">:</span> <span class="s1">'src/main/frontend'</span><span class="p">,</span>
<span class="na">src</span><span class="p">:</span> <span class="p">[</span><span class="s1">'**/*.es6'</span><span class="p">],</span>
<span class="na">dest</span><span class="p">:</span> <span class="s1">'target/generated-resources'</span><span class="p">,</span>
<span class="na">ext</span><span class="p">:</span> <span class="s1">'.js'</span><span class="p">,</span>
<span class="na">extDot</span><span class="p">:</span> <span class="s1">'first'</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div>
<h2 id="module-names">Module names</h2>
<p>The configuration above generates module names based on the relative file path of our ES6 files. So if we have a file at <code>src/main/frontend/web-resource/example-app/lib/utils.es6</code> the contents of that file will be converted into a file in <code>target/generated-resources/web-resource/example-app/lib/utils.js</code> with a module name of <code>example-app/lib/utils</code>.</p>
<p>Before:</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">import</span> <span class="nx">$</span> <span class="nx">from</span> <span class="s2">"jquery"</span><span class="p">;</span>
<span class="kr">import</span> <span class="nx">flag</span> <span class="nx">from</span> <span class="s2">"aui/flag"</span><span class="p">;</span> <span class="c1">// Check me out, using a module provided by AUI!
</span>
<span class="kr">export</span> <span class="kd">function</span> <span class="nx">somethingGreat</span><span class="p">(){};</span>
</code></pre></div>
<p>After:</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">define</span><span class="p">(</span><span class="s2">"example-app/lib/utils"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"exports"</span><span class="p">,</span> <span class="s2">"jquery"</span><span class="p">,</span> <span class="s2">"aui/flag"</span><span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">exports</span><span class="p">,</span> <span class="nx">_jquery</span><span class="p">,</span> <span class="nx">_auiFlag</span><span class="p">)</span> <span class="p">{</span>
<span class="s2">"use strict"</span><span class="p">;</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">exports</span><span class="p">,</span> <span class="s2">"__esModule"</span><span class="p">,</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">});</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">somethingGreat</span> <span class="o">=</span> <span class="nx">somethingGreat</span><span class="p">;</span>
<span class="c1">//... removed for brevity
</span><span class="p">});</span>
</code></pre></div>
<h2 id="build-lifecycle">Build lifecycle</h2>
<p>To speed up development we use <a href="https://www.npmjs.com/package/grunt-contrib-watch">grunt-contrib-watch</a> to automagically recompile our ES6 files when they change.</p>
<p>With the following Gruntfile.js configuration all we need to do when we make changes to our source files is to refresh the page in the browser!</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">grunt</span><span class="p">.</span><span class="nx">initConfig</span><span class="p">({</span>
<span class="na">watch</span><span class="p">:</span> <span class="p">{</span>
<span class="na">modules</span><span class="p">:</span> <span class="p">{</span>
<span class="na">files</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'src/main/frontend/**/*.es6'</span>
<span class="p">],</span>
<span class="na">tasks</span><span class="p">:</span> <span class="p">[</span><span class="s1">'build'</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="c1">// the babel config from above goes here...
</span><span class="p">});</span>
<span class="c1">// This creates a new task called 'build' that will run the 'babel' task and
// the 'concat' task defined elsewhere.
</span><span class="nx">grunt</span><span class="p">.</span><span class="nx">registerTask</span><span class="p">(</span><span class="s1">'build'</span><span class="p">,</span> <span class="p">[</span>
<span class="s1">'babel'</span><span class="p">,</span>
<span class="s1">'concat'</span><span class="p">,</span>
<span class="c1">//'uglify'
</span><span class="p">]);</span>
</code></pre></div>
<p>Typically, when I kick off the <code>atlas-debug</code> command for a project in a terminal I'll open up a new terminal tab/session and run <code>grunt build watch</code> as well.</p>
<p>We also have a useful <code>grunt clean</code> command for removing only the generated Javascript files:</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">grunt</span><span class="p">.</span><span class="nx">registerTask</span><span class="p">(</span><span class="s1">'clean'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">grunt</span><span class="p">.</span><span class="nx">file</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="s2">"target/generated-resources"</span><span class="p">);</span>
<span class="nx">grunt</span><span class="p">.</span><span class="nx">file</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="s2">"target/generated-test-resources"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>
<h2 id="source-structure">Source structure</h2>
<p>When we're developing JS-heavy Atlassian add-ons it can take a lot of time having to maintain an up-to-date web-resource configuration in the atlassian-plugin.xml.</p>
<p>To reduce the manual XML editing overhead we developed a <a href="https://bitbucket.org/Adaptavist/dynamic-web-modules">dynamic web modules</a> add-on that will automatically add web-resources to your plugin without you needed to specify each one.</p>
<h2 id="things-to-note">Things to note</h2>
<p>A couple of the things that took me a little while to wrap my head around are:</p>
<ol>
<li><p>Modules don't auto-run. Module code won't execute unless we want it to. They are not <a href="http://benalman.com/news/2010/11/immediately-invoked-function-expression/">IIFE</a>s. We need to a) load the modules we want into our application, and b) call an exported function of those modules to start the execution. Often we have a vanila JS file that uses the almond.js library to load the relevant modules for our application, and then calls some of the exported functions of those modules to 'start' them.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// setup.js
</span><span class="nx">require</span><span class="p">([</span><span class="s1">'example-app/setup'</span><span class="p">,</span> <span class="s1">'adaptavist/analytics'</span><span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">setup</span><span class="p">,</span> <span class="nx">analytics</span><span class="p">){</span>
<span class="nx">setup</span><span class="p">.</span><span class="nx">bindEvents</span><span class="p">();</span>
<span class="nx">setup</span><span class="p">.</span><span class="nx">renderApp</span><span class="p">();</span>
<span class="nx">analytics</span><span class="p">.</span><span class="nx">register</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></li>
<li><p>Modules are singletons, so any state within a module is shared whenever that module is imported. If we want to create different 'instances' of whatever our module represents, we need to ensure our module exports some kind of constructor which encapsulates the state.</p></li>
</ol>
All kinds of things I learnt trying to performance test against Fisheye/Crucible2016-03-17T00:00:00+00:00http://labs.adaptavist.com/performance/2016/03/17/all-kinds-of-things-i-learnt-trying-to-performance-test-against-fisheyecrucible<h2 id="preamble">Preamble</h2>
<p>Recently I was working on a project where we had a JIRA add-on and a Fisheye/Crucible (Fecru) add-on that communicate via
applinks and the Fecru add-on was performing slowly.</p>
<p>A colleague had used <a href="http://gatling.io">Gatling</a> to run performance tests before and as a company we've started using
<a href="http://arquillian.org/">Arquillian</a> a lot more to <a href="https://bitbucket.org/Adaptavist/atlassian-arquillian-containers">run integration tests</a>.
I figured there must be a way to combine those two technologies with the Atlassian SDK to let me run a single command
and have JIRA & Fecru startup, run an Arquillian test (which would configure the apps and populate them with data) and
then run the Gatling test, giving me nice <a href="http://junit.org/">JUnit</a> output for my Bamboo build.</p>
<p>Here's what I learnt along the way!</p>
<h1 id="1-gatling-tests-aren-39-t-very-straightforward-to-execute-programmatically">1. Gatling tests aren't very straightforward to execute programmatically</h1>
<p>I was hoping Gatling would have some lovely Java API that I could just call from within an integration test so that this
would just all work but that was wishful thinking. The <a href="http://gatling.io/docs/2.1.7/quickstart.html">official documentation</a>
says to use a <a href="https://github.com/gatling/gatling/blob/61c46fb25b285eff728c1d6377a3a1d235d3ced4/gatling-bundle/src/universal/bin/gatling.sh">bundled shell script</a>
that builds up a classpath using a bunch of environment variables and the <em>find</em> command on *nix. Alternatively, I could
also use the <a href="http://gatling.io/docs/2.1.7/extensions/maven_plugin.html">Maven plugin</a> to kick off the tests, but running
Maven from within an integration test didn't feel quite right...</p>
<p><em>Side-note: If the Atlassian SDK allowed us more fine grained control over integration tests that would
definitely have helped here. As far as I know, there isn't a pre-integration-test phase or a post-integration-test phase
that would allow us to run things before/after the apps have started and before/after they get terminated.</em></p>
<h1 id="2-ctrl-d-does-not-send-a-signal">2. CTRL-D does not send a signal</h1>
<p>By this point I'd resigned myself to writing some kind of bash script to do the job and so my initial bash (pun intended)
at a solution looked something like this:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash</span>
atlas-debug --instanceId jira -pl performance-tests -DskipTests 2>&1 >jira.log &
atlas-debug --instanceId fecru -pl performance-tests -DskipTests 2>&1 >fecru.log &
</code></pre></div>
<p>What's not to like?</p>
<p>When I ran this script each application would start up, and then immediately shutdown without any prompting or interaction or
anything. It turns out, after a bit of digging and <del>manic Googling</del> research, that this is all to do with I/O streams.</p>
<p>For those who don't already know, on *nix systems, each process has three I/O streams by default: stdin, stdout and stderr.
These are used to direct the input into that process and two kinds of output (regular output and error messages). When I
run <code>atlas-debug</code> from my terminal/console stdout gets set to my terminal and I see a whole load of Maven output about
all the things its downloading. Interestingly, stdin also gets set to my terminal - i.e. it reads whatever I type on my
keyboard.</p>
<p>The funny <code>2>&1</code> and <code>>jira.log</code> stuff in the bash script above allows me to redirect the output of the atlas-debug
process into a file called jira.log. The trailing <code>&</code> tells bash to run the preceding command in the background. Now,
the important thing to note here is that when a process is told to run in the background it no longer has a stdin stream
associated with it.</p>
<p>OK, enough rambling. What does that have to do with anything?</p>
<p>I mistakenly assumed that, like CTRL-C or CTRL-Z, CTRL-D sent a signal to the currently attached process (the one with
stdin associated with my terminal) and that signal is what the process responded to in order to shutdown. In particular
I'm talking about atlas-debug here and the nice way it says to:</p>
<blockquote>
<p>[INFO] Type Ctrl-D to shutdown gracefully<br>
[INFO] Type Ctrl-C to exit</p>
</blockquote>
<p>It turns out that CTRL-D is actually the magic key combination for the End Of Transmission (EOT) character, which is
ASCII char 4 and Unicode U+0004.</p>
<p><em>Having run <code>man ascii</code> before I'm not sure how I missed that... </sarcasm></em></p>
<p>Anyway, that means that CTRL-D sends a kind of "end of stdin" message to a process, and when I was running atlas-debug
in the background (and therefore stdin was being detached) it was receiving the same "end of stdin" message and shutting
JIRA and Fecru down when they'd finished starting up.</p>
<p>While it is convenient to be able to gracefully shutdown an Atlassian application with a couple key presses, this
implementation using CTRL-D means that its not possible to send an interrupt signal to trigger a graceful shutdown which
makes automating the startup/shutdown of an application harder.</p>
<p><em>Side-note: Having just looked through the source of <a href="https://github.com/Adaptavist/avst-app">avst-app</a> (Adaptavist's 'package
manager' for Atlassian Applications) it appears you can do graceful shutdowns <a href="https://github.com/Adaptavist/avst-app/blob/master/share/avst-app/lib/tomcat/stop.d/99stop">via tomcat</a>.</em></p>
<h1 id="3-screen-is-awesome">3. Screen is awesome</h1>
<p>So now that I understood why I couldn't just run JIRA and Fecru as background processes I turned my attention towards an
application called <em>screen</em> that I'd heard of before but never actually used. Here's some excerpts from the man page:</p>
<blockquote>
<p>Screen is a full-screen window manager that multiplexes a physical terminal between several processes (typically
interactive shells)... When screen is called, it creates a single window with a shell in it (or the specified command)
and then gets out of your way... All windows run their programs completely independent of each other. Programs continue
to run when their window is currently not visible...</p>
</blockquote>
<p>Sounds perfect for my use case here. A few command line options later I had modified my earlier script as follows:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash</span>
screen -d -m -s /bin/bash -L atlas-debug --instanceId jira -pl performance-tests -DskipTests
screen -d -m -s /bin/bash -L atlas-debug --instanceId fecru -pl performance-tests -DskipTests
</code></pre></div>
<p>Throw in a little extra grep magic to check the screen output log for "jira started successfully" and "fecru started
successfully" and we've got something we can work with.</p>
<p>Once I knew the applications had started I could run <code>atlas-mvn verify -pl performance-tests -DnoWebapp=true</code>
to run my Arquillian test that would populate both applications with data, swiftly followed by <code>atlas-mvn gatling:execute -pl performance-tests</code>
to run my Gatling tests.</p>
<p><em>Side-note: I'm pretty sure a similar solution is possible using <a href="http://www.linuxjournal.com/article/2156">named pipes</a>,
but I couldn't quite get it working as I expected it to...</em></p>
<h1 id="4-gatling-literally-just-implemented-junit-output">4. Gatling literally just implemented JUnit output</h1>
<p>At this point I had a script that looked a little like this (I've omitted some of it for brevity):</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash</span>
screen -d -m -s /bin/bash -L atlas-debug --instanceId jira -pl performance-tests -DskipTests
screen -d -m -s /bin/bash -L atlas-debug --instanceId fecru -pl performance-tests -DskipTests
<span class="c"># Omitted checks here for ensuring the processes are running and obtaining their process IDs</span>
<span class="k">while</span> <span class="o">[[</span> -z <span class="s2">"</span><span class="sb">`</span>grep <span class="s2">"fecru started successfully"</span> screenlog.0<span class="sb">`</span><span class="s2">"</span> <span class="o">||</span> <span class="se">\</span>
-z <span class="s2">"</span><span class="sb">`</span>grep <span class="s2">"jira started successfully"</span> screenlog.0<span class="sb">`</span><span class="s2">"</span> <span class="o">]]</span>; <span class="k">do
</span>sleep 5
<span class="k">done
</span>atlas-mvn verify -pl performance-tests -DtestGroups<span class="o">=</span>perf -DnoWebapp<span class="o">=</span><span class="nb">true
</span>atlas-mvn gatling:execute -pl performance-tests
</code></pre></div>
<p>But I had originally wanted to run this in a Bamboo build, so I needed the Gatling test output in a useful format. A
quick <del>Google</del> Github later and I found <a href="https://github.com/gatling/gatling/issues/2636">this closed issue</a> that
provided JUnit output format to Gatling tests based on the <a href="http://gatling.io/docs/2.0.0-RC2/general/assertions.html">assertions</a>
specified for the simulation/test. Yay!</p>
<h1 id="5-being-a-good-bash-citizen">5. Being a good Bash citizen</h1>
<p>Whilst developing the script I made extensive use of the <code>-x</code> flag in my bash script:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash -x</span>
...
</code></pre></div>
<p>I don't know of many ways to debug bash scripts, but this flag is great as it prints out all the lines that are being
executed with variables substituted for their values so you can actually see what is being executed and where the script
is bombing out or failing to do what you expect.</p>
<p>Additionally, there are a few other options I used to make my script '<a href="https://sipb.mit.edu/doc/safe-shell/">safer</a>' to
run - namely <code>-u</code> (treat unset variables as an error) and <code>-o pipefail</code> (return error code if any commands in a
pipeline error, instead of only the last command). </p>
<p>Finally I figured I should probably handle interrupts cleanly and shutdown JIRA and Fecru if the script was terminated
itself. It turns out this is fairly easy to do with the following snippet of code which I stuck at the top of my script:</p>
<div class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash -u -o pipefail</span>
<span class="c"># High default PIDs for the kill signal trap, these will be overwritten once JIRA and</span>
<span class="c"># Fecru processes have been started</span>
<span class="nv">JIRA_PID</span><span class="o">=</span>1000000
<span class="nv">FECRU_PID</span><span class="o">=</span>1000000
<span class="k">function </span>killApps <span class="o">{</span>
<span class="nb">echo</span> <span class="s2">"Interrupted, terminating JIRA and Fecru"</span>
<span class="nb">kill</span> -HUP <span class="k">${</span><span class="nv">JIRA_PID</span><span class="k">}</span> <span class="k">${</span><span class="nv">FECRU_PID</span><span class="k">}</span>
<span class="nb">exit </span>1
<span class="o">}</span>
<span class="nb">trap </span>killApps SIGHUP SIGINT SIGTERM
</code></pre></div>Adaptavist’s Holiday Gift of Atlassian Deployment Automation2015-12-24T00:00:00+00:00http://labs.adaptavist.com/labs/2015/12/24/christmas-present-opensource<h1 id="adaptavist-automation">Adaptavist Automation</h1>
<p>Adaptavist have deployed thousands of Atlassian systems over the years and have been a part of shaping best practice around running Atlassian applications. For the last 5 years, our operations teams have focussed on automating the deployment, monitoring and management of these systems for ourselves and our clients. We code-named this initiative MaMa (standing for MaMa: Application Management Automation), and we are now ready to introduce it to the wider world.</p>
<h2 id="why-create-mama">Why Create MaMa?</h2>
<p>Adaptavist are big believers in the power of the agile movement and the continuous... well, everything that it enables. Underpinning the success of all this is reliable repeatability through automation of tasks.</p>
<p>Several years ago a major client of ours asked us to assist them using <a href="https://puppetlabs.com/">Puppet</a> to deploy the full suite of Atlassian products for a major release. This started out with just deploying Java, the Atlassian applications and a few supporting aspects (such as databases). Now it is able to automate the full lifecycle of entire virtual-machines from scratch on cloud infrastructure.</p>
<p>Through other open source tooling, as well as what MaMa provides, Adaptavist are now able to create and recreate entire environments within public cloud providers, like <a href="http://aws.amazon.com/">Amazon Web Services</a>, at the push of a button with nothing more than some tracked configuration.</p>
<p>Our vision is to provide a robust set of tooling that enables self-service for deployments for both technical and non-technical users. The tooling we’re releasing now underpins this vision, and is the first step of sharing the infrastructure Adaptavist enjoy.</p>
<h2 id="open-sourcing">Open Sourcing</h2>
<p>We and our customers have benefitted from the open source community through the Linux, Java, Ruby communities and it’s only right that we give back in support of the projects we have used and extended, such as Puppet, Hiera, Fog and many, many more.</p>
<p>So today, Adaptavist are open sourcing MaMa, which can be found on <a href="https://github.com/Adaptavist/">GitHub</a>. We have chosen <a href="http://www.apache.org/licenses/LICENSE-2.0">Version 2 of the Apache Software License</a> to release under, as we believe in supporting business that wish to take our code and build on it, and not encumber anyone unnecessarily. But obviously, we’d like you to share the extensions you’ve built with the world too!</p>
<h2 id="what-problems-does-mama-solve">What Problems Does MaMa Solve?</h2>
<p>Adaptavist believe in loosely coupled tooling which integrate clearly in an opinionated way about the process, but allowing any of the technology to be replaced by better alternatives as they come along. The tool set Adaptavist is open sourcing today includes:</p>
<h3 id="avst-puppet">avst-Puppet</h3>
<p>Adaptavist’s Puppet support project which tries to make things as data driven through Hiera configuration as possible, wrapping other modules to make them useable from Hiera where they are not out of the box. Avst-puppet comes as a set of Puppet modules and a data driven template structure.</p>
<ul>
<li><p>Configuration of the OS and OS-packaged applications</p></li>
<li><p>Configures supporting elements such as web-servers, databases, ldap etc.</p></li>
<li><p>Configures and runs avst-app</p></li>
<li><p>Support encrypted secrets and facts, so credentials can be stored securely within configurations</p></li>
</ul>
<p>This includes:</p>
<ul>
<li><p><a href="https://github.com/Adaptavist/puppet-runner">Puppet runner</a> - A wrapper runner for puppet that allows for encrypted secrets</p></li>
<li><p><a href="https://github.com/Adaptavist/hiera-fragment">Hiera Fragment</a> - Merges yaml files for use with secrets</p></li>
<li><p><a href="https://github.com/Adaptavist/hiera_loader">Hiera Loader</a> - Simplifies looking up values from hiera with programmatic defaults</p></li>
</ul>
<h3 id="avst-wizard"><a href="https://github.com/Adaptavist/avst-wizard">avst-wizard</a></h3>
<ul>
<li><p>Completes the setup wizard based on configuration files</p></li>
<li><p>Supports JIRA Server, Confluence Server, Bitbucket Server, FishEye/Crucible, Bamboo Server and Crowd</p></li>
<li><p>Automates user directory, database and Atlassian application base url configuration</p></li>
</ul>
<h3 id="avst-app"><a href="https://github.com/Adaptavist/avst-app">avst-app</a></h3>
<ul>
<li><p>Written in Bash (with native OS packaging)</p></li>
<li><p>Configures and manages non-natively packaged applications</p>
<ul>
<li>e.g. Atlassian applications, Artifactory/Nexus, Coverity</li>
</ul></li>
</ul>
<h2 id="adaptavist’s-opinionated-process">Adaptavist’s Opinionated Process</h2>
<p>Technology only automates a process, but what that process is, in many ways, is more important.</p>
<p>Adaptavist have 5 principles which underpin MaMa:</p>
<ul>
<li><p>Loosely coupled elements following the <a href="https://en.wikipedia.org/wiki/Single_responsibility_principle">Single Responsibility Principle</a></p></li>
<li><p>The separation of configuration and code</p></li>
<li><p>Everything, including configuration, should be version controlled, composable and repeatable</p></li>
<li><p>Linux support first</p></li>
<li><p>Cloud first, with Vagrant support</p></li>
</ul>
<h2 id="what’s-next">What’s Next?</h2>
<p>We’re still investing in MaMa, and there’s more tools in our kit that will be open sourced over time. Notable tools are:</p>
<h3 id="avst-cloud">avst-cloud</h3>
<ul>
<li><p>Drives public cloud infrastructure in a vendor independent way</p></li>
<li><p>Ruby, based around fog.io</p></li>
<li><p>Takes hiera configuration describing infrastructure</p></li>
<li><p>Bootstraps the OS until Puppet can take over</p></li>
</ul>
<h3 id="avst-backup">avst-backup</h3>
<ul>
<li><p>Abstracts various backup technologies to provide a consistent interface for avst-app</p></li>
<li><p>Ruby, highly modular</p></li>
<li><p>Currently knows how to backup and restore: databases, LDAP and file-systems</p></li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<p>Right now, the MaMa has been released as a set of tools, where each of the components are usable in their own right. The coming avst-cloud component will enable the use of the other MaMa components in a more cohesive manner.</p>
<p>For now, the tools are ready for use and we’re investing in making the documentation around how to use the tools as part of a platform ready for public consumption. For now, please visit us at <a href="http://labs.adaptavist.com/">Adaptavist Labs</a> and on <a href="https://github.com/Adaptavist/">GitHub</a>, to follow our progress.</p>
<p>Watch this space!</p>
Getting a Custom Field value safely2015-12-17T00:00:00+00:00http://labs.adaptavist.com/code/java/jira/2015/12/17/getting-custom-field-values-safely<h2 id="how-hard-can-it-be">How hard can it be?</h2>
<p>So, in our code we retrieve a custom field and we have an issue, and we just get the custom field value right?</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">CustomField</span> <span class="n">customField</span> <span class="o">=</span> <span class="n">customFieldManager</span><span class="o">.</span><span class="na">getCustomFieldObjectByName</span><span class="o">(</span><span class="s">"My Multi-select"</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">Object</span> <span class="n">value</span> <span class="o">=</span> <span class="n">issue</span><span class="o">.</span><span class="na">getCustomFieldValue</span><span class="o">(</span><span class="n">customField</span><span class="o">);</span>
</code></pre></div>
<p>Great, now we're done!</p>
<h2 id="object-that-39-s-no-use">Object?! That's no use...</h2>
<p>Hmmm, Object isn't going to be very helpful. Let's assume it's a List<String> because that makes sense for a multiselect right?</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">CustomField</span> <span class="n">customField</span> <span class="o">=</span> <span class="n">customFieldManager</span><span class="o">.</span><span class="na">getCustomFieldObjectByName</span><span class="o">(</span><span class="s">"My Multi-select"</span><span class="o">);</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">>)</span> <span class="n">issue</span><span class="o">.</span><span class="na">getCustomFieldValue</span><span class="o">(</span><span class="n">customField</span><span class="o">);</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span>
</code></pre></div>
<p>What could possibly go wrong?</p>
<h2 id="nullpointerexception">NullPointerException</h2>
<p>Ah yes, that. Wait... <a href="http://stackoverflow.com/questions/101072/java-why-arent-nullpointerexceptions-called-nullreferenceexceptions">does Java even have pointers</a>?!</p>
<p>So it turns out we forgot to assign a value to our custom field in JIRA. Oops.</p>
<p>Let's fix that "edge case" up quickly.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">CustomField</span> <span class="n">customField</span> <span class="o">=</span> <span class="n">customFieldManager</span><span class="o">.</span><span class="na">getCustomFieldObjectByName</span><span class="o">(</span><span class="s">"My Multi-select"</span><span class="o">);</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">>)</span> <span class="n">issue</span><span class="o">.</span><span class="na">getCustomFieldValue</span><span class="o">(</span><span class="n">customField</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">value</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"No value assigned to custom field 'My Multi-select' on issue {}"</span><span class="o">,</span> <span class="n">issue</span><span class="o">.</span><span class="na">getKey</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div>
<p>Alright, sorted. Nice.</p>
<h2 id="classcastexception">ClassCastException</h2>
<div class="highlight"><pre><code class="language-" data-lang="">java.lang.ClassCastException: com.atlassian.jira.issue.customfields.option.LazyLoadedOption cannot be
cast to java.lang.String
</code></pre></div>
<p>D'oh! Well, at least we know what's really returned by the <a href="https://docs.atlassian.com/jira/latest/com/atlassian/jira/issue/Issue.html#getCustomFieldValue-com.atlassian.jira.issue.fields.CustomField-">Issue#getCustomFieldValue</a>
for a multi-select now!</p>
<p>OK, so we can just cast to the right kind of List now right?</p>
<p><em>NB: Annoyingly, Java won't throw a ClassCastException here if the returned List is of a different type the type its being cast to.</em></p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">CustomField</span> <span class="n">customField</span> <span class="o">=</span> <span class="n">customFieldManager</span><span class="o">.</span><span class="na">getCustomFieldObjectByName</span><span class="o">(</span><span class="s">"My Multi-select"</span><span class="o">);</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">LazyLoadedOption</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">LazyLoadedOption</span><span class="o">>)</span> <span class="n">issue</span><span class="o">.</span><span class="na">getCustomFieldValue</span><span class="o">(</span><span class="n">customField</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">value</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"No value assigned to custom field 'My Multi-select' on issue {}"</span><span class="o">,</span> <span class="n">issue</span><span class="o">.</span><span class="na">getKey</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">LazyLoadedOption</span> <span class="n">opt</span> <span class="o">=</span> <span class="n">value</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="n">opt</span><span class="o">.</span><span class="na">getValue</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div>
<p>Great, let's tidy this up into a useful method!</p>
<h2 id="refactoring">Refactoring</h2>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="cm">/**
* Useful method for retrieving multi-select custom field value as List<String>
*/</span>
<span class="nd">@Nonnull</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">getMultiSelectFieldValue</span><span class="o">(</span><span class="nd">@Nonnull</span> <span class="n">String</span> <span class="n">fieldName</span><span class="o">,</span> <span class="nd">@Nonnull</span> <span class="n">Issue</span> <span class="n">issue</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Validate</span><span class="o">.</span><span class="na">notNull</span><span class="o">(</span><span class="n">fieldName</span><span class="o">);</span>
<span class="n">Validate</span><span class="o">.</span><span class="na">notNull</span><span class="o">(</span><span class="n">issue</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">CustomField</span> <span class="n">customField</span> <span class="o">=</span> <span class="n">customFieldManager</span><span class="o">.</span><span class="na">getCustomFieldObjectByName</span><span class="o">(</span><span class="n">fieldName</span><span class="o">);</span>
<span class="c1">// Let's use the Option interface here as well instead of a specific implementation</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">Option</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">Option</span><span class="o">>)</span> <span class="n">issue</span><span class="o">.</span><span class="na">getCustomFieldValue</span><span class="o">(</span><span class="n">customField</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"No value assigned to custom field 'My Multi-select' on issue {}"</span><span class="o">,</span> <span class="n">issue</span><span class="o">.</span><span class="na">getKey</span><span class="o">());</span>
<span class="k">return</span> <span class="n">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// Oooo, Java 8! Shiny!</span>
<span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Option:</span><span class="o">:</span><span class="n">getValue</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div>
<p>Now we're clearly finished right?</p>
<h2 id="do-you-trust-your-jira-admin">Do you trust your JIRA Admin?</h2>
<p><strong>Don't.</strong></p>
<p>Remember, rule 101 about external input to computer programs?</p>
<p><em>You can't trust anyone!</em></p>
<p>How is our shiny new method going to cope if the custom field we've told it to use isn't a multi-select?</p>
<div class="highlight"><pre><code class="language-" data-lang="">java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
</code></pre></div>
<p>Our good friend ClassCastException again!</p>
<h2 id="let-39-s-get-defensive">Let's get defensive</h2>
<p>Let's check we actually have what we're expecting!</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="cm">/**
* Useful method for retrieving multi-select custom field value as List<String>
*/</span>
<span class="nd">@Nonnull</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">getMultiSelectFieldValue</span><span class="o">(</span><span class="nd">@Nonnull</span> <span class="n">String</span> <span class="n">fieldName</span><span class="o">,</span> <span class="nd">@Nonnull</span> <span class="n">Issue</span> <span class="n">issue</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Validate</span><span class="o">.</span><span class="na">notNull</span><span class="o">(</span><span class="n">fieldName</span><span class="o">);</span>
<span class="n">Validate</span><span class="o">.</span><span class="na">notNull</span><span class="o">(</span><span class="n">issue</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">CustomField</span> <span class="n">customField</span> <span class="o">=</span> <span class="n">customFieldManager</span><span class="o">.</span><span class="na">getCustomFieldObjectByName</span><span class="o">(</span><span class="n">fieldName</span><span class="o">);</span>
<span class="c1">// Let's use the Option interface here as well instead of a specific implementation</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">Option</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">Option</span><span class="o">>)</span> <span class="n">issue</span><span class="o">.</span><span class="na">getCustomFieldValue</span><span class="o">(</span><span class="n">customField</span><span class="o">);</span>
<span class="c1">// Handle NullPointerException</span>
<span class="k">if</span> <span class="o">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span>
<span class="s">"No value assigned to custom field '{}' on issue {}. Returning empty list."</span><span class="o">,</span>
<span class="n">customField</span><span class="o">,</span> <span class="n">issue</span><span class="o">.</span><span class="na">getKey</span><span class="o">()</span>
<span class="o">);</span>
<span class="k">return</span> <span class="n">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// Handle non-list return values</span>
<span class="k">if</span> <span class="o">(!(</span><span class="n">value</span> <span class="k">instanceof</span> <span class="n">List</span><span class="o">))</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span>
<span class="s">"Value of custom field '{}' on issue {} was not a List. Returning empty list."</span><span class="o">,</span>
<span class="n">customField</span><span class="o">,</span> <span class="n">issue</span><span class="o">.</span><span class="na">getKey</span><span class="o">()</span>
<span class="o">);</span>
<span class="k">return</span> <span class="n">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// If it's empty, lets just return a new empty string list and forget about the origin type</span>
<span class="k">if</span> <span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// Handle potential ClassCastException for lists of any other kind, like Label</span>
<span class="k">if</span> <span class="o">(!(</span><span class="n">value</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span> <span class="k">instanceof</span> <span class="n">Option</span><span class="o">))</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span>
<span class="s">"Value of custom field '{}' on issue {} was not a List<Option>. Returning empty list."</span><span class="o">,</span>
<span class="n">customField</span><span class="o">,</span> <span class="n">issue</span><span class="o">.</span><span class="na">getKey</span><span class="o">()</span>
<span class="o">);</span>
<span class="k">return</span> <span class="n">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// Oooo, Java 8! Shiny!</span>
<span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Option:</span><span class="o">:</span><span class="n">getValue</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div>
<h2 id="overkill">Overkill?</h2>
<p>That depends on how you want failures to be treated within your code.</p>
<p>The code above essentially fails silently, trying to recover from all possible scenarios by returning an empty data set.
This may work perfectly in some scenarios, but be a disaster in others.</p>
<p>As the developer you need to work with the customer to determine how each of these edge cases should be handled. Most of
the additional code in the example above handles exceptional circumstances, so perhaps throwing an Exception would be the
correct thing to do - it just often feels like a nasty user experience if the UI breaks and displays a huge stack trace.</p>
<p>At least with the code above the behaviour degrades gracefully and if a user isn't seeing what they expect they can flag
that up with someone who can probably ask someone else to look at the logs and find out what's going wrong.</p>
<p>At the very least as developers we should be asking the questions that led us to this result, rather than only
<a href="https://en.wikipedia.org/wiki/Happy_path">coding the happy path</a>.</p>
Putting Google Analytics to work with plugins for Confluence2015-12-07T00:00:00+00:00http://labs.adaptavist.com/confluence/2015/12/07/using-google-analytics-to-track-plugin-usages-in-confluence<h2 id="background">Background</h2>
<p>Deciding to add a new feature to a plugin can be challenging, because it is difficult to know what could be useful. Getting it right means useful functionality has been added, which creates value for the customer and business.
Getting it wrong can be a net loss, and continue to be a burden to maintain while remaining largely unused. Worst yet...how do you even know if it is being used? What we need is some metric by which we can see if a feature is used by the client base. Google Analytics (from here on referred to as GA) to the rescue!</p>
<p>With GA we can see how often a feature is used and discern usage patterns. It could also help diagnose possible sticking points. This in time will help us understand where we should be spending our resources for maximum effect.
I should mention that there are several alternatives to GA. We decided to use GA because they have well defined documentation, and some of the groundwork for our project was already researched in a previous spike. Google's size also invokes some
confidence that they will continue to provide the service. What we want to avoid is maintenance as a result of a service becoming unusable. We are planning on integrating GA to most of our plugins, so a reliable persisting service is a requirement.</p>
<h2 id="introduction">Introduction</h2>
<p>It is important to state that no confidential or sensitive information is collected. None of the analytical data can be associated with any one Confluence instance. We are only capturing usage patterns.
In normal use cases <a href="https://www.google.co.uk/analytics/">GA</a> can track and report website traffic. The challenging bit is to try and implement this functionality in a plugin for Confluence, with as little code as possible added to the plugin itself.
We might want to do this so we can track the usages of different parts of a plugin. In this case we want to measure actions and macro usage related to our plugin. This GA project will be loaded as a dependency in the plugin where we want to implement analytics.</p>
<h2 id="capturing-plugin-actions-with-a-servlet-filter">Capturing plugin actions with a Servlet Filter</h2>
<p>What we required was a generic way of capturing plugin actions while ignoring Confluence actions. XWork Interceptors might provide similar functionality, however we did run into some issues and time constraints prohibited us from fully exploring it further.
The Servlet filter only intercepts those actions which are related to our plugin,for instance to see how often the configuration section of the plugin is entered. We can adjust this behaviour by changing the url-pattern associated with the plugin actions specified in the atlassian-plugin.xml file.</p>
<p>Care should be taken not to capture Confluence actions. This can be avoided by adopting a clear naming scheme to your own plugin actions. The url-pattern specified for the Servlet filter does not have to be complex.
In the Servlet filter we are intercepting the HTTP request analysing the uri, establishing which action is being called, and then recording it. For those actions which have multiple responsibilities we are also recording the parameter for each HTTP request so we can distinguish between requests.
For example: .../confluence/admin/pluginNameHere/configuration-add-edit.action?createConfig=true&name= here we might only want to record /pluginNameHere/configuration-add-edit.action and createConfig. This will depend on how your plugin was designed and how you wish to record analytic data.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">doFilter</span><span class="p">(</span><span class="n">ServletRequest</span> <span class="n">servletRequest</span><span class="o">,</span> <span class="n">ServletResponse</span> <span class="n">servletResponse</span><span class="o">,</span> <span class="n">FilterChain</span> <span class="n">filterChain</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span><span class="o">,</span> <span class="n">ServletException</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">analyticsManager</span><span class="o">.</span><span class="na">isAnalyticsEnabled</span><span class="o">())</span> <span class="o">{</span>
<span class="n">HttpServletRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="o">(</span><span class="n">HttpServletRequest</span><span class="o">)</span> <span class="n">servletRequest</span><span class="o">;</span>
<span class="n">String</span> <span class="n">valueToSend</span> <span class="o">=</span> <span class="n">findUri</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">valueToSend</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">NO_URI_FOUND</span><span class="o">))</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">requestParameters</span> <span class="o">=</span> <span class="n">findLinkQueryString</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">requestParameters</span><span class="o">))</span> <span class="o">{</span>
<span class="n">valueToSend</span> <span class="o">=</span> <span class="n">valueToSend</span><span class="o">.</span><span class="na">concat</span><span class="o">(</span><span class="s">" "</span><span class="o">).</span><span class="na">concat</span><span class="o">(</span><span class="n">findLinkQueryString</span><span class="o">(</span><span class="n">request</span><span class="o">));</span>
<span class="o">}</span>
<span class="n">CommonAnalyticsData</span> <span class="n">analytics</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CommonAnalyticsData</span><span class="o">(</span><span class="n">pluginHolder</span><span class="o">,</span> <span class="s">"Action: "</span> <span class="o">+</span> <span class="n">valueToSend</span><span class="o">);</span>
<span class="n">googleAnalyticsService</span><span class="o">.</span><span class="na">sendDataToAnalytics</span><span class="o">(</span><span class="n">analytics</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">filterChain</span><span class="o">.</span><span class="na">doFilter</span><span class="o">(</span><span class="n">servletRequest</span><span class="o">,</span> <span class="n">servletResponse</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div>
<p>The first bit of code simply checks if analytics is enabled and proceeds if it is.
Then we are capturing the uri and formatting it into something we can understand when observed in the analytics data.
We then send the formatted data, plugin name and version to our own buffer and dispatch service.</p>
<h2 id="client-allowed-to-disable-analytics">Client allowed to disable analytics</h2>
<p>To comply with Google's terms and conditions, the user should be allowed to disable analytics. We implement the switch as a System Property which is true by default. The user can disable analytics at the command prompt when launching Confluence.</p>
<h2 id="delivering-your-payload-to-google">Delivering your payload to Google</h2>
<p>Google has a nice guide to help understand the <a href="https://developers.google.com/analytics/devguides/collection/protocol/v1/reference#cache-busting">process.</a></p>
<h2 id="managing-the-sending-of-data-to-google">Managing the sending of data to Google</h2>
<p>Google's free service only allows a certain amount of hits to their servers per month based on the <a href="https://support.google.com/analytics/answer/1070983?hl=en">terms of service.</a>
We want to remain under the limit to make use of the free service, without potentially loosing out on analytic data once the threshold is reached.
To do this we implement a buffer which sends one large payload when the threshold is reached.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">addDataToBuffer</span><span class="p">(</span><span class="n">CommonAnalyticsData</span> <span class="n">analyticsData</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Integer</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">dataToSend</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">analyticsData</span><span class="o">))</span> <span class="o">{</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">dataToSend</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">analyticsData</span><span class="o">)</span> <span class="o">+</span> <span class="mi">1</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">dataToSend</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">analyticsData</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="n">isBufferFull</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">totalItemsToSend</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="n">Map</span><span class="o">.</span><span class="na">Entry</span><span class="o"><</span><span class="n">CommonAnalyticsData</span><span class="o">,</span> <span class="n">Integer</span><span class="o">></span> <span class="n">entry</span> <span class="o">:</span> <span class="n">dataToSend</span><span class="o">.</span><span class="na">entrySet</span><span class="o">())</span> <span class="o">{</span>
<span class="n">totalItemsToSend</span> <span class="o">+=</span> <span class="n">entry</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">totalItemsToSend</span> <span class="o">>=</span> <span class="n">pluginHolder</span><span class="o">.</span><span class="na">getLicenseTier</span><span class="o">().</span><span class="na">getMaxUsers</span><span class="o">()</span> <span class="o">*</span> <span class="mi">10</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="n">emptyBuffer</span><span class="o">()</span> <span class="o">{</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"empty the buffer"</span><span class="o">);</span>
<span class="n">dataToSend</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
</code></pre></div>
<p>Here we are just collecting all the analytics data, incrementing a counter, and then comparing the counter against our customised threshold.</p>
<h2 id="determining-the-analytics-buffer-size">Determining the analytics buffer size</h2>
<p>It is important to realise that a 1000 user instance would generate a lot more analytical data than a 10 user instance.
Therefore we want to adjust the buffer threshold to accommodate the size of the Confluence user base. In simple terms all we need to do is find out what licence was purchased and use that information to adjust the threshold.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="n">LicenseTier</span> <span class="nf">getLicenseTier</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">ServiceReference</span> <span class="n">serviceReference</span> <span class="o">=</span> <span class="n">bundleContext</span><span class="o">.</span><span class="na">getServiceReference</span><span class="o">(</span><span class="s">"com.atlassian.upm.api.license.PluginLicenseManager"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">serviceReference</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">LicenseTier</span><span class="o">.</span><span class="na">TEN</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">Object</span> <span class="n">pluginLicenseManager</span> <span class="o">=</span> <span class="n">bundleContext</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="n">serviceReference</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pluginLicenseManager</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">Object</span> <span class="n">optionLicense</span> <span class="o">=</span> <span class="n">call</span><span class="o">(</span><span class="n">pluginLicenseManager</span><span class="o">,</span> <span class="s">"getLicense"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isTrue</span><span class="o">(</span><span class="n">call</span><span class="o">(</span><span class="n">optionLicense</span><span class="o">,</span> <span class="s">"isDefined"</span><span class="o">)))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">LicenseTier</span><span class="o">.</span><span class="na">TEN</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">Object</span> <span class="n">optionUsers</span> <span class="o">=</span> <span class="n">call</span><span class="o">(</span><span class="n">call</span><span class="o">(</span><span class="n">optionLicense</span><span class="o">,</span> <span class="s">"get"</span><span class="o">),</span> <span class="s">"getMaximumNumberOfUsers"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">optionUsers</span><span class="o">.</span><span class="na">toString</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"none()"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">LicenseTier</span><span class="o">.</span><span class="na">UNLIMITED</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">Integer</span> <span class="n">users</span> <span class="o">=</span> <span class="o">(</span><span class="n">Integer</span><span class="o">)</span> <span class="n">call</span><span class="o">(</span><span class="n">call</span><span class="o">(</span><span class="n">call</span><span class="o">(</span><span class="n">optionLicense</span><span class="o">,</span> <span class="s">"get"</span><span class="o">),</span> <span class="s">"getMaximumNumberOfUsers"</span><span class="o">),</span> <span class="s">"get"</span><span class="o">);</span>
<span class="k">return</span> <span class="n">LicenseTier</span><span class="o">.</span><span class="na">getLicenseTier</span><span class="o">(</span><span class="n">users</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Unable to obtain license status from Atlassian licensing"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">LicenseTier</span><span class="o">.</span><span class="na">TEN</span><span class="o">;</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">bundleContext</span><span class="o">.</span><span class="na">ungetService</span><span class="o">(</span><span class="n">serviceReference</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>Here we are querying the Atlassian UPM service for information about the license tier the instance is using. It's important because if the threshold is high for a small instance you might not get the analytic data.
Alternatively if the threshold is low for a large instance, you might reach your service limit.</p>
<h2 id="sorting-the-analytic-data">Sorting the analytic data</h2>
<p>Google provides 4 categories to use. Below we show how we decided to arrange the captured analytic data, and include a picture of how it appears in GA.</p>
<p>Category: Confluence Version, e.g. Confluence 5.8.4
Action: Plugin Version, e.g. Forms for Confluence 5.1-beta2-SNAPSHOT
Label: Represents the type of data retrieved: e.g. Macro Usage, Action Triggered, or License Tier
Value: Aggregated results, e.g. Macro Usage: ClearMacro - 57 times.</p>
<p><img src="/images/analytics-screen-shot.png" alt="My helpful screenshot"></p>
<p>Above we see that the ClearMacro was used 57 times, and that this data was delivered in one payload.</p>
<h2 id="capturing-analytics-from-a-macro-using-polymorphism">Capturing analytics from a macro using Polymorphism</h2>
<p>If you want to collect data related to the usage of a macro, your macro should extend the AbstractAnalyticsMacro provided by your analytics project.
This abstract class is the one fired when a macro is executed. It delegates the execution to the sub-classes using the abstract method renderMacro,
and then collects and sends analytic data in the "finally" clause.</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractAnalyticsMacro</span> <span class="kd">implements</span> <span class="n">Macro</span> <span class="o">{</span>
<span class="o">[..]</span>
<span class="nd">@Override</span>
<span class="nd">@RequiresFormat</span><span class="o">(</span><span class="n">Format</span><span class="o">.</span><span class="na">Storage</span><span class="o">)</span>
<span class="kd">public</span> <span class="n">String</span> <span class="n">execute</span><span class="o">(</span><span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">params</span><span class="o">,</span> <span class="n">String</span> <span class="n">body</span><span class="o">,</span> <span class="n">ConversionContext</span> <span class="n">context</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">MacroExecutionException</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">renderMacro</span><span class="o">(</span><span class="n">params</span><span class="o">,</span> <span class="n">body</span><span class="o">,</span> <span class="n">context</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="s">"preview"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getOutputType</span><span class="o">()))</span> <span class="o">{</span>
<span class="n">CommonAnalyticsData</span> <span class="n">analytics</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CommonAnalyticsData</span><span class="o">(</span><span class="n">pluginHolder</span><span class="o">.</span><span class="na">getPluginNameAndVersion</span><span class="o">(),</span> <span class="s">"Macro Usage: "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">());</span>
<span class="n">googleAnalyticsService</span><span class="o">.</span><span class="na">sendDataToAnalytics</span><span class="o">(</span><span class="n">analytics</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="kd">abstract</span> <span class="n">String</span> <span class="n">renderMacro</span><span class="o">(</span><span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">params</span><span class="o">,</span> <span class="n">String</span> <span class="n">body</span><span class="o">,</span> <span class="n">ConversionContext</span> <span class="n">context</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">MacroExecutionException</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div>
<h2 id="conclusion">Conclusion</h2>
<p>In the coming weeks we should see analytics data trickling in which will assist us in making data driven decisions. This project is still a work in progress, and I expect it to evolve over time as we make adjustments.
I appreciate not all points were touched on, but we did try to make it short and sweet.</p>
Devoxx Voting, A retrospective2015-12-02T00:00:00+00:00http://labs.adaptavist.com/code/2015/12/02/devoxx-be-voting-a-retrospective<h2 id="devoxx">Devoxx</h2>
<p>Over the last 10 years or so a group of <a href="http://adaptavist.com">Adaptavist</a> engineers have attended <a href="http://devoxx.be">Devoxx BE</a> regularly. <a href="https://twitter.com/dhardiker">Dan Hardiker</a> is a member
of the steering committee, one of the founding members of <a href="http://devoxx.co.uk">Devoxx UK</a> and heavily involved in <a href="http://devoxx4kids.org">Devoxx4Kids</a> along with
many other Adaptavist employees (including myself). Often we have been involved in providing support in running
the conference, from helping with moving tables to creating interactive screens outside session rooms that can be interacted with
using a <a href="http://www.xbox.com/en-GB/xbox-one/accessories/kinect-for-xbox-one">Kinect</a> gesture tracker. This year was no exception as we provided an API to record votes for sessions and a UI
to view the <a href="http://devoxxuk.github.io/conf-2015/">top talks</a>.</p>
<h2 id="why-voting">Why Voting?</h2>
<p>Organising an event as large as Devoxx BE (over 3500 attendees) takes some serious planning and feedback from people
attending sessions is vital to ensuring the success of the event and future events. Speakers are also interested in
feedback on how their talk went and what people want to hear about. A star rating, as coarse as it is, is a useful data
point for determining the popularity of sessions and topics, and can be used to establish trends.</p>
<p>In previous years voting has been performed by presenting RFID badges at readers on the way out of rooms which sometimes
caused queues on the way out and prevented those that were staying in the room from voting as they would have to leave
and re-enter in order to record a vote. This year <a href="https://twitter.com/stephan007">Stephan</a> <a href="http://www.devoxx.be/team-members/stephan-janssen-2/">Janssen</a>, who runs Devoxx BE and oversees the Devoxx family, wanted to experiment with voting from the schedule apps
available on iOS, Android and Windows Phone devices.</p>
<p>As it happens Dan and <a href="https://twitter.com/mrhazell">Mark</a> <a href="www.devoxx.co.uk/team-members/mark-hazell/">Hazel</a>, who organises Devoxx UK and co-founded <a href="http://voxxed.com/">Voxxed</a>, had wanted to do something similar for Devoxx UK which a few Adaptavist Engineers had
worked on...</p>
<h2 id="vote-api">Vote API</h2>
<p>The vote API was conceived over a few conference calls and a decision was made to write a microservice in <a href="https://golang.org/">Go</a>. The
service needed to:</p>
<ul>
<li>Accept Votes</li>
<li>Display the top talks
<ul>
<li>Overall</li>
<li>By Day</li>
<li>By Track</li>
<li>By Type</li>
</ul></li>
<li>Cope with the conference load</li>
</ul>
<p>Session and speaker information needed to be fetched from the CFP REST service and returned to the UI. The UI needed to:</p>
<ul>
<li>Display the top talks</li>
<li>Refresh regularly</li>
<li>Cope with network failures (conference wifi)</li>
</ul>
<h2 id="some-stats">Some Stats</h2>
<h4 id="voting">Voting:</h4>
<table><thead>
<tr>
<th>Vote Stat</th>
<th>Value</th>
</tr>
</thead><tbody>
<tr>
<td>Valid Votes</td>
<td>13,337</td>
</tr>
<tr>
<td>Mean Vote</td>
<td>3.9</td>
</tr>
<tr>
<td>Median Vote</td>
<td>4</td>
</tr>
<tr>
<td>Mode Vote</td>
<td>5</td>
</tr>
</tbody></table>
<h4 id="performance">Performance:</h4>
<table><thead>
<tr>
<th>Performance Stat</th>
<th>Value (as measured by reverse proxy)</th>
</tr>
</thead><tbody>
<tr>
<td>Total Requests</td>
<td>1,701,003</td>
</tr>
<tr>
<td>Peak Load per Hour</td>
<td>40,246</td>
</tr>
<tr>
<td>Peak Load per Minute</td>
<td>839</td>
</tr>
<tr>
<td>Peak Load per Second</td>
<td>103</td>
</tr>
<tr>
<td>Mean response time</td>
<td>8ms</td>
</tr>
<tr>
<td>Median response time</td>
<td>8ms</td>
</tr>
<tr>
<td>Maximum response time</td>
<td>152ms</td>
</tr>
</tbody></table>
<h2 id="so-how-did-it-go">So how did it go?</h2>
<p>Go is a very interesting language that has lots of opinions. Some parts of the language feel quite modern whilst others
are like stepping back in time. I was particularly happy with the ease with which the application could be deployed (we
used <a href="https://www.heroku.com/">Heroku</a> for hosting but it was easy to run dev environments and in a Docker container). Performance was also
impressive. The app was able to cope with a peak of 600 requests per minute on a single node using only 7MB of RAM (as
reported by Heroku's monitoring), with a median response time of 8ms and a 95th percentile of 50ms.</p>
<p>I'll summarise quickly my experience with go:</p>
<h3 id="things-i-like-about-go">Things I like about go</h3>
<ul>
<li>Dev Speed and tools
<ul>
<li>Compiler is fast</li>
<li>Code formatter - all go code looks the same</li>
</ul></li>
<li>Concurrency</li>
<li>Speed with which other picked up the code</li>
<li>Multiple return values</li>
<li>Consistency. Code looks like same (as long as go fmt is used) in pretty much every repository</li>
<li>Inheritance model feels well thought out</li>
<li>Lots of libraries</li>
</ul>
<h3 id="things-i-don-39-t-like">Things I don't like</h3>
<ul>
<li>Dependency Management - this is a disaster
<ul>
<li>godeps helps, but URIs to git repositories is not really sufficient for stable dependency management</li>
</ul></li>
<li>Tabs for indentation (flame war)</li>
<li>The Type System - or lack there of. <code>interface{}</code> === <code>Object</code> in Java</li>
<li>Error handling
<ul>
<li>How do I determine if I can handle an <code>Error</code>?</li>
<li>Stacktrace is elusive, which makes debugging and root cause analysis difficult</li>
<li>Error handling code often gets in the way of logic</li>
<li>Convention says no value returned when an <code>Error</code> is not nil</li>
<li>Note I am not arguing for try/catch blocks everywhere</li>
</ul></li>
</ul>
<h3 id="would-i-use-go-again">Would I use go again?</h3>
<p>I'm undecided right now. It feels like a good language for a mass participation open source project due to the
consistency and the low barrier to entry. I would also use it on a project that required deployment to constrained
systems, both in terms of resources or ability to install dependencies. Go's deployment is highly elegant. Would I write
the vote API in Go again? probably not. One reason is that it was an opportunity to learn and I'd go for another new
language or library next time out.</p>
Some things I've learnt about SingleSelect2015-11-25T00:00:00+00:00http://labs.adaptavist.com/code/groovy/2015/11/25/some-things-Ive-learnt-about-singleselect<p>TL;DR
Don't use it! Use <a href="https://docs.atlassian.com/aui/latest/docs/auiselect2.html">Select2</a> instead</p>
<h2 id="how-data-is-stored-internally">How data is stored internally</h2>
<p>Using a data attribute on the option elements themselves... I didn't even know you could store arbitrary JS objects as attributes on HTML elements... somehow...
It turns out that it is jQuery that enables this. If you're curious see the <a href="https://github.com/jquery/jquery/blob/master/src/data/Data.js">jQuery documentation</a></p>
<p>Each item/option in a SingleSelect is represented by a ItemDescriptor object:</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">ItemDescriptor</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'jira/ajs/list/item-descriptor'</span><span class="p">);</span>
<span class="k">new</span> <span class="nx">ItemDescriptor</span><span class="p">({</span>
<span class="na">title</span><span class="p">:</span> <span class="s2">"Some awesome tooltip text that nobody will ever see"</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="mi">12345</span><span class="p">,</span>
<span class="na">label</span><span class="p">:</span> <span class="s2">"Pick me!"</span><span class="p">,</span>
<span class="na">allowDuplicate</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">});</span>
</code></pre></div>
<p>These ItemDescriptor objects are stored and retrieved from the "descriptor" data attribute on an element (there are a few additional to the one you've specified as the target for your SingleSelect) in the DOM.</p>
<p>Here's some code from JIRA to prove it:</p>
<p>src/jira-project/jira-components/jira-webapp-common/src/main/webapp/includes/ajs/select/SelectModel.js</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="cm">/**
* Gets an array of JSON descriptors for selected options
*
* @method getDisplayableSelectedDescriptors
* @return {Array}
*/</span>
<span class="nx">getDisplayableSelectedDescriptors</span><span class="err">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">descriptors</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$element</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s2">"option:selected"</span><span class="p">).</span><span class="nx">not</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_getExclusions</span><span class="p">()).</span><span class="nx">each</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">descriptors</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">jQuery</span><span class="p">.</span><span class="nx">data</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s2">"descriptor"</span><span class="p">));</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">descriptors</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p><span style="color: red"><em>Don't do that. The DOM was not invented as a data store for arbitrary JS objects.</em></span></p>
<h2 id="how-options-are-filtered">How options are filtered</h2>
<p>Not using the suggestionsHandler... that only handles providing suggestions (select options). Filtering those suggestions based on what you type is performed using the "matchingStrategy" option...</p>
<p>Also, the matchingStrategy option is a string which is converted into a RegExp and is also used for highlighting the part of each option that matches what you've typed.</p>
<p>So it all breaks if your matchingStrategy option value doesn't fit the structure that the highlighting code is expecting.</p>
<p>Here's the code used to do the highlighting of segments of your options based on what you've typed:</p>
<p>src/jira-project/jira-components/jira-webapp-common/src/main/webapp/includes/ajs/list/List.js</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">labelRegex</span> <span class="o">=</span> <span class="s2">"(^|.*?(\\s+|\\())({0})(.*)"</span><span class="p">;</span> <span class="c1">// Default matchingStrategy value: match start of words, including after '('
</span><span class="kd">var</span> <span class="nx">replacementText</span> <span class="o">=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">label</span><span class="p">().</span><span class="nx">replace</span><span class="p">(</span><span class="nx">labelRegex</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">prefix</span><span class="p">,</span> <span class="nx">spaceOrParenthesis</span><span class="p">,</span> <span class="nx">match</span><span class="p">,</span> <span class="nx">suffix</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">div</span> <span class="o">=</span> <span class="nx">jQuery</span><span class="p">(</span><span class="s2">"<div>"</span><span class="p">);</span>
<span class="nx">prefix</span> <span class="o">&&</span> <span class="nx">div</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">jQuery</span><span class="p">(</span><span class="s2">"<span>"</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="nx">prefix</span><span class="p">));</span>
<span class="nx">div</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">jQuery</span><span class="p">(</span><span class="s2">"<em>"</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="nx">match</span><span class="p">));</span>
<span class="nx">suffix</span> <span class="o">&&</span> <span class="nx">div</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">jQuery</span><span class="p">(</span><span class="s2">"<span>"</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="nx">suffix</span><span class="p">));</span>
<span class="k">return</span> <span class="nx">div</span><span class="p">.</span><span class="nx">html</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div>
<p>An exercise for the reader is to generate a different matchingStrategy regex that will handle full-text matching, instead of just start-of-word matching, whilst retaining the highlight functionality as implemented above...</p>
<p>To prevent filtering (and handle filtering within your suggestionsHandler) set the matchingStrategy option to something falsey like "" or false.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">_</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'underscore'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">SingleSelect</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'jira/ajs/select/single-select'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">DefaultSuggestHandler</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'jira/ajs/select/suggestions/default-suggest-handler'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">searchFulltext</span> <span class="o">=</span> <span class="nx">DefaultSuggestHandler</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="na">formatSuggestions</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">descriptors</span><span class="p">,</span> <span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">query</span> <span class="o">==</span> <span class="s2">""</span><span class="p">)</span> <span class="k">return</span> <span class="nx">descriptors</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">_</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">descriptors</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">descriptor</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">descriptor</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">label</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="o">></span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="kd">var</span> <span class="nx">mySingleSelect</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SingleSelect</span><span class="p">({</span>
<span class="na">element</span><span class="p">:</span> <span class="nx">el</span><span class="p">,</span>
<span class="na">suggestionsHandler</span><span class="p">:</span> <span class="nx">searchFulltext</span><span class="p">,</span>
<span class="na">matchingStrategy</span><span class="p">:</span> <span class="s2">""</span> <span class="c1">// This prevents SingleSelect from subsequent filtering of the
</span> <span class="c1">// options provided by the suggestionsHandler
</span><span class="p">});</span>
</code></pre></div>
<h2 id="how-to-override-the-stupid-behaviour-of-jira-39-s-dialog-smart-forms-during-save-validation">How to override the stupid behaviour of JIRA's dialog smart forms during save/validation</h2>
<p>So, JIRA has some JS that triggers an "enable" event on all input's in a form during an AJAX save of that form. Obviously. Oh, and it removes the "disabled" attribute from those input fields as well, for good measure.</p>
<p>SingleSelect's listen for such events and will therefore enable when an AJAX form is submitted, even if validation of the form fails... like when you forget to fill in a Summary or something...</p>
<p>You need to create your own implementation of a SingleSelect that re-disables the input field if required:</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">ConsistentlyDisabledSingleSelectRegardlessOfHowHardJIRATries</span> <span class="o">=</span> <span class="nx">SingleSelect</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="na">init</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">options</span><span class="p">){</span>
<span class="c1">// Work around for JIRA's stupid behaviour of enabling all form fields on form AJAX submit
</span> <span class="k">this</span><span class="p">.</span><span class="nx">_events</span><span class="p">.</span><span class="nx">container</span><span class="p">.</span><span class="nx">enable</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="kc">false</span> <span class="cm">/* some business logic should go here */</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">enable</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Apply the 'disabled' attribute again, because JIRA's already removed it by this point...
</span> <span class="k">this</span><span class="p">.</span><span class="nx">$container</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'input'</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s1">'disabled'</span><span class="p">,</span> <span class="s1">'disabled'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="c1">// Obviously JS is classical OO inheritance, not prototypal...
</span> <span class="k">this</span><span class="p">.</span><span class="nx">_super</span><span class="p">(</span><span class="nx">options</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div>Using SOY for JIRA actions2015-10-15T00:00:00+01:00http://labs.adaptavist.com/2015/10/15/using-soy-for-jira-actions<p>So it turns out you can use SOY templates in JIRA webwork actions, but its not
a straightforward switch.</p>
<h2 id="soy">Soy</h2>
<p>First you need to convert your template to soy:</p>
<div class="highlight"><pre><code class="language-soy" data-lang="soy">{namespace Magical.templates}
/**
* Render the main UI
* @param something
*/
{template .main}
<html>
...
</html>
{/template}
</code></pre></div>
<h2 id="web-resource">Web-resource</h2>
<p>Then add a web-resource to declare your soy file:</p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><web-resource</span> <span class="na">key=</span><span class="s">"action-templates"</span><span class="nt">></span>
<span class="nt"><resource</span> <span class="na">type=</span><span class="s">"soy"</span> <span class="na">name=</span><span class="s">"does-not-matter"</span> <span class="na">location=</span><span class="s">"somewhere/magic-action.soy"</span><span class="nt">/></span>
<span class="nt"></web-resource></span>
</code></pre></div>
<h2 id="webwork">Webwork</h2>
<p>Then adjust the webwork action to refer to it:</p>
<div class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><webwork1</span> <span class="na">key=</span><span class="s">"some-magic"</span> <span class="na">class=</span><span class="s">"java.lang.Object"</span><span class="nt">></span>
<span class="nt"><actions></span>
<span class="nt"><action</span> <span class="na">name=</span><span class="s">"com.example.MagicAction"</span> <span class="na">alias=</span><span class="s">"Magic"</span><span class="nt">></span>
<span class="nt"><view</span> <span class="na">type=</span><span class="s">"soy"</span> <span class="na">name=</span><span class="s">"input"</span><span class="nt">></span>:action-templates/Magical.templates.main<span class="nt"></view></span>
<span class="nt"></action></span>
<span class="nt"></actions></span>
<span class="nt"></webwork1></span>
</code></pre></div>
<p>Notice the view refers to the web-resource module key: <strong>:action-templates</strong> -
this is short form for <strong><em>my-plugin-key</em>:action-templates</strong>, followed by the
fully namespaced template name after a slash.
We don't need to know the file location or resource name here.</p>
<h2 id="watch-it-fail">Watch it fail</h2>
<p>Then give it a whirl, and watch it fail ;)</p>
<h2 id="annotate-like-crazy">Annotate like crazy</h2>
<p>We don't get any parameters passed to the template at all by default, which in
someways is good, but it means extra work adapting your action.</p>
<p>You no longer have an <strong>$action</strong> parameter either.</p>
<p>To pass data into the template you need to annotate the methods in your action
with <strong>@ActionViewData</strong>, eg:</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@ActionViewData</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">getSomething</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">...;</span>
<span class="o">}</span>
</code></pre></div>
<p>The data from these methods is gathered prior to rendering, and not called
on-demand, as velocity does.
Also if you want to get data from a method higher in the inheritance
hierarchy you'll need to override it and annotate it, eg:</p>
<div class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@ActionViewData</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">getReturnUrl</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">getReturnUrl</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div>
<p>You can no longer call arbitrary methods from your action, <strong>@ActionViewData</strong>
only supports methods that return something and take no parameters.</p>
<p>Although you can return any object from these methods and a soy mapper will
handle resolving keys to method calls, as best as it can.
If you need more control over the process, either format and return new data,
or use a <strong>SoyDataMapper</strong> - I haven't tried this yet though.</p>
<p><strong>@ActionViewData</strong> does allow you to specify an alternative parameter name
for the data return by the method, and you can also limit which views the
data will be collected for.</p>
<p><strong>@ActionViewDataMappings</strong> is similar but allows specifying of several
action views.</p>
<h2 id="i18n">I18n</h2>
<p>Wherever you used to do this:</p>
<div class="highlight"><pre><code class="language-velocity" data-lang="velocity">${i18n.getText('foo')}
</code></pre></div>
<p>you'll do:</p>
<div class="highlight"><pre><code class="language-soy" data-lang="soy">{getText('foo')}
</code></pre></div>
<p>but <em>BEWARE</em>, the argument has to be a literal string, this will <strong>not</strong> work:</p>
<div class="highlight"><pre><code class="language-soy" data-lang="soy">{getText($myI18nKey)}
</code></pre></div>
<p>you'll have to resolve dynamic i18n keys in your action beforehand.</p>
Object Reflection in Groovy2015-09-26T00:00:00+01:00http://labs.adaptavist.com/code/groovy/2015/09/26/object-reflection-in-groovy<p><em>Actually, I think this just uses the Java reflection packages, but anyway...</em></p>
<p>I'd not done any reflection before in Java or Groovy and ended up wanting to do some for a workflow export script for
a customer.</p>
<p>Specifically I wanted to iterate over the public fields of a class as they were being used as constants to store custom field names:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomFields</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">CF_PLATFORM</span> <span class="o">=</span> <span class="s2">"Platform Response"</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">CF_FUNC_TEAM</span> <span class="o">=</span> <span class="s2">"Functional Team Response"</span>
<span class="c1">// Platforms relevant to specific requirement
</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">CF_IMPACTED_PLATFORMS</span> <span class="o">=</span> <span class="s2">"Impacted Platform/s"</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">CF_LEAD_PLATFORM</span> <span class="o">=</span> <span class="s1">'Lead Platform'</span>
<span class="c1">// Corresponding Groups for Issue Security Level
</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">CF_IMPACTED_OFFERING_GROUPS</span> <span class="o">=</span> <span class="s2">"Impacted Offering Groups"</span>
<span class="c1">// cross-product field representing those who have not replied
</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">CF_NO_RESPONSE_FROM</span> <span class="o">=</span> <span class="s2">"No response from"</span>
<span class="c1">// cross-product field representing those who said No Impact
</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">CF_NO_IMPACT</span> <span class="o">=</span> <span class="s2">"No Impact Platforms/Teams"</span>
<span class="o">}</span>
</code></pre></div>
<p>Turns out its quite easy, you can do the following to get a list of the values of those public fields:</p>
<div class="highlight"><pre><code class="language-groovy" data-lang="groovy">
<span class="kd">final</span> <span class="n">CustomFields</span> <span class="n">cf</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CustomFields</span><span class="o">()</span>
<span class="kd">final</span> <span class="n">Field</span><span class="o">[]</span> <span class="n">customFieldDefns</span> <span class="o">=</span> <span class="n">CustomFields</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredFields</span><span class="o">()</span>
<span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">values</span> <span class="o">=</span> <span class="n">customFieldDefns</span><span class="o">.</span><span class="na">findAll</span> <span class="o">{</span>
<span class="c1">// For those who don't know, the magic variable 'it' here refers to the current element in the collection
</span> <span class="n">Modifier</span><span class="o">.</span><span class="na">isPublic</span><span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">modifiers</span><span class="o">)</span>
<span class="o">}.</span><span class="na">collect</span> <span class="o">{</span> <span class="n">Field</span> <span class="n">f</span> <span class="o">-></span>
<span class="c1">// This returns the value of the field from the cf object (instance of CustomFields)
</span> <span class="n">f</span><span class="o">.</span><span class="na">name</span> <span class="o">+</span> <span class="s2">" has value: "</span> <span class="o">+</span> <span class="n">f</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">cf</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div>
<p>Now <code>values</code> contains the entries:</p>
<ul>
<li>CF_PLATFORM has value: Platform Response</li>
<li>CF_FUNC_TEAM has value: Functional Team Response</li>
<li>etc :)</li>
</ul>
Introducing Adaptavist Labs2015-09-22T00:00:00+01:00http://labs.adaptavist.com/labs/2015/09/22/introducing-adaptavist-labs<h2 id="why-adaptavist-labs">Why Adaptavist Labs?</h2>
<p><em>What is this site about?</em></p>
<p>Through out our 10 year history Adapavist has been an active member of the Atlassian developer community from
contributing to the plugin system that underlies all add-ons to writing the precursor to the Universal Plugin Manager.
We believe that the community around Atlassian is vital and it is our intention on this site to share some of our
experience writing add-ons, automating the tools, builds and use of the Atlassian (and associated) tools from a
development and operations perspective. This site comes from the delivery teams within Adaptavist who work on products,
bespoke development and our managed services offerings. Adaptavist Labs will also serve as a platform and hub for our
open source projects. </p>
<p>No guarantees are made about frequency of posts and content comes with no warranty - YMMV, buyer beware etc.</p>