atlascamp 2014: static connect add-ons
DESCRIPTION
Building Atlassian Connect add-ons with client-server frameworks like Express.JS and Play produces powerful add-ons, but requires hosting and database consideration. In contrast, "static" Connect add-ons are easier to write and far simpler to deploy. A static Connect add-on is simply a set of files (including an atlassian-connect.json descriptor) that are web accessible, either through a web server or via deployment to a CDN. Static add-ons have several advantages. First, infinite scalability. Why pay for CPU when you can let the user agent do the work? Second, simple persistence. Why pay for disk when you have local storage and JIRA & Confluence's persistence REST APIs? Third, extreme performance. Let a worldwide CDN serve your files from the nearest edge - it's literally impossible to compete with a static add-on when it comes to performance! Finally, easy caching. Why bother serving requests when your unchanged static files can be cached indefinitely with simple HTTP caching? In this talk I'll highlight the benefits and restrictions of static add-on architecture, and discuss the nuts and bolts of implementing a static add-on. I'll also show off the static add-on skeleton that will get your static add-on development off to a flying start.TRANSCRIPT
June 3-5, 2014 | Berlin, Germany
@kannonboy
Timothy Pettersen, Developer Advocate, Atlassian
Static Connect Add-ons
BRACE YOURSELVESBRACE YOURSELVES
ATLASSIAN CONNECT IS COMINGATLASSIAN CONNECT IS COMING
1/7TH OF A CENT PER USER PER MONTH?1/7TH OF A CENT PER USER PER MONTH?
MY FANTASTIC MARKETPLACE PROFITS SHOULD COVER THAT
MY FANTASTIC MARKETPLACE PROFITS SHOULD COVER THAT
Fantastic Profits
Outrageous Profits
Fantastic Profits
Demo Time
Traditional Web Apps
HTML CSS JS
Browser App Server
GET /some/resource
POST /some/form
Static Web Apps
HTML CSS JS
Browser Web Server
GET /some/resource
Static Web Apps
HTML CSS JS
Browser CDN
GET /some/resource
Cheap & Fast!
Easy to cache
Static Add-Ons
HTML CSS JS
Browser
GET /resource
Full REST API
CDN
X-Domain JavaScript API
4 reasons why static add-ons are awesome
Performance
Scalability
Security
Dev Loop
Static Connect Add-ons
LICENSING
STORING STUFF
ANATOMY OF A STATIC ADD-
WHAT IF I NEED A BACKEND?
Static Connect Add-ons
LICENSING
STORING STUFF
ANATOMY OF A STATIC ADD-
WHAT IF I NEED A BACKEND?
ANATOMY OF A STATIC ADD-
Static Add-Ons
Browser HTML CSS JS
GET /resourceX-Domain JavaScript API
CDN
GET /atlassian-connect.json
Static Add-Ons
Browser CDNHTML CSS JS
GET /resourceX-Domain JavaScript API
GET /atlassian-connect.json
Authentication
authentication: {! "type": “none”!}!
scopes: [! “read”, “write”,!! “delete”, “admin”!]!
Callbacks“lifecycle”: {! "installed": "/installed",! "uninstalled": "/uninstalled",! "enabled": "/enabled",! "disabled": "/disabled"!}
"webhooks": [{! “event": "jira:issue_created",! "url": "/issue-created"!}]
Pages & Web Fragments
“adminPages”: [..],!“generalPages”: [..],!“profilePages”: [..],!“configurePage”: {}
“webSections”: [..],!“webPanels”: [..],!“webItems”: [..]!
Tab Panels
“jiraProjectAdminTabPanels”: [..],!“jiraIssueTabPanels”: [..],!“jiraComponentTabPanels”: [..],!“jiraProfileTabPanels”: [..],!“jiraProjectTabPanels”: [..],!“jiraVersionTabPanels”: [..],!“spaceToolsTabs”: [..]!
Macros
“dynamicContentMacros”: [..]!
“staticContentMacros”: [..]!
Static Add-Ons
HTML CSS JS
Browser
GET /resource
CDN
GET /atlassian-connect.json
X-Domain JavaScript API
Static Add-Ons
HTML CSS JS
Browser
GET /resource
CDN
GET /atlassian-connect.json
X-Domain JavaScript API
Static Connect Boilerplate<head>! <link rel="stylesheet" href=“//aui-cdn.atlassian.com/../aui.css”> ! <script src=“//cdnjs.cloudflare.com/../jquery.js”></script>! <script src=“//aui-cdn.atlassian.com/../aui.js”></script>! <!--[if IE]>! <script src=“//aui-cdn.atlassian.com/../aui-ie.js”></script>! <![endif]—>! <script src=“//aui-cdn.atlassian.com/../include-all.js”></script>!! <script src=“//somecdn.com/../space-graph.js”></script>! <link rel="stylesheet" href=“//somecdn.com/../space-graph.css”>!</head>!..
no-cache
Static Caching
Browser CDN
HTML
JS, CSS
Cache-Control:
Expires: Tue, 3 Aug 2032 12:00:00 GMT
public
Versioned URLs<head>! <script src=“//mycdn/../v1/space-graph.js”></script>! <link rel="stylesheet" href=“//mycdn/../v1/space-graph.css”>!</head>!..
<head>! <script src=“//mycdn/../v2/space-graph.js”></script>! <link rel="stylesheet" href=“//mycdn/../v2/space-graph.css”>!</head>!..
Context parametersgeneralPages: [{! "key": "space-graph",! "url": "/space-graph.html?spaceKey={space.key}",! "location": "system.content.action",! "name": {! "value": "Space Graph"! }!}]
<iframe src=“https://somecdn.com/space-graph.html?spaceKey=JRA”><iframe src=“https://somecdn.com/space-graph.html?spaceKey=CONF”>!<iframe src=“https://somecdn.com/space-graph.html?spaceKey=…”>
Static Add-Ons
HTML CSS JS
Browser
GET /resource
CDN
GET /atlassian-connect.json
X-Domain JavaScript API
Static Add-Ons
HTML CSS JS
Browser
GET /resource
CDN
GET /atlassian-connect.json
X-Domain JavaScript API
include-all.jsfunction includeAll(callback) {!
// parse query parameters from web-panel iframe URL!
var host = getQueryParam(“xdm_e”);!
var contextPath = getQueryParam(“cp”);!
!// construct URL targeting host application!
var allUrl = host + contextPath + “/atlassian-connect/all.js";!
!// retrieve script asynchronously!
jQuery.getScript(allUrl, function () {!
// window.AP is now available !
// can now launch pop-ups, use REST APIs and interact with host UI!
callback(window.AP);!
});!
}
AP APIincludeAll(function(AP) {!
! // use the REST API!
! AP.request({!
! ! url: “/rest/prototype/1/space/" + spaceKey + ".json?expand=rootpages”,!
! ! type: “GET”,!
! ! success: function(spaceJson) {!
! ! ! // parse root pages from space object!
! ! }!
! });!
! !
! // display messages!
! AP.messages.info(“Space Graph”, “Initializing Space Graph for ” + spaceKey);!
!! // plus AP.events, AP.dialog, AP.cookie, etc.!
}
Static Connect Add-ons
STORING STUFF
ANATOMY OF A STATIC ADD-
WHAT IF I NEED A BACKEND?
LICENSING
//obfuscated!var _0x81b0=["\x6C\x69\x63","\x61\x63\x74\x69\x76\x65","\x65\x78\x70\x69\x72\x65\x64"];var d=a(_0x81b0[0]);switch(d){case _0x81b0[1]:break;case _0x81b0[2]:b();break;case _0x81b0[1]:c();break;}
// minified!var d=a("lic");switch(d){case"active":break;case"expired":b();break;case"active":c();break}
lic parameterswitch (getQueryParameter("lic")) {! case "active":! // license is valid! break;! case "expired":! doExpired();! break;! case "none":! doUnlicensed();! break;!}
We’ve got your back
Static Connect Add-ons
LICENSING
ANATOMY OF A STATIC ADD-
WHAT IF I NEED A BACKEND?
STORING STUFF
Third-party cookies
<iframe>
AP.cookie// AP.cookie!// .save(name, value, expires);!// .read(name, callback);!// .erase(name);
// example: display a welcome message once per user!AP.cookie.read(“seen.welcome.message”, function(val) {!!!! if (val !== “true”) {!! ! displayFirstTimeWelcomeMessage();!! ! AP.cookie.save(“seen.welcome.message”, “true”, 365);!! }!! !});!
@Tim Pettersen: this page looks old. Please check if we can delete it.
Confluence Content Properties// example: store a JSON object against the selected page!var propertyKey = “note”;!var propertyValue = {created: todaysDate, body: bodyText};
// POST -> CREATE, GET -> RETRIEVE, PUT -> UPDATE, DELETE -> DELETE
AP.request({!! url: “/rest/api/1/experimental/content/“ + pageId + ”/property”,!! type: “POST”,! contentType: “application/json”,!! data: JSON.stringify({key: propertyKey, value: propertyValue}) !});
JIRA Entity Properties// example: store a JSON object against an issue!var propertyKey = “note”;!var propertyValue = {created: todaysDate, body: bodyText};!!AP.request({!! url: “/rest/api/2/issue/“ + issueKey + “/properties/” + propertyKey,!! type: “POST”,! contentType: “application/json”,!! data: JSON.stringify(propertyValue)!});
// POST -> CREATE, GET -> RETRIEVE, PUT -> UPDATE, DELETE -> DELETE
Indexing Entity Properties"jiraEntityProperties": [{! "name": {! "value" : ”Issue Notes"! },! "entityType": "issue",! "keyConfigurations": [{! "propertyKey" : "note",! "extractions" : [{! "objectName" : "created",! "type" : “date"! }, {!! ! ! ! "objectName" : "created",! "type" : “text" // or number, string!! ! }]! }]!}]!!
ALSO AVAILABLE IN P2
JQL: ! issue.property[note].bodyText ~ “foo” or! issue.property[note].created > startOfMonth()
Storage Options
AP.cookieVery fast (local)!
Scoped to the browser!
Size limited!
Temporary storage!
Use for user preference, recent
history, etc.
Entity storageSlower (ajax roundtrip)!
Scoped to the entity!
Size limited!
Permanent storage !
Use for annotating issues or pages with
data
Backend
!?
Static Connect Add-ons
STORING STUFF
LICENSING
ANATOMY OF A STATIC ADD-
WHAT IF I NEED A BACKEND?
Are you sure?
Are you sure?
Analytics Mapping Realtime Charting Rendering
Imaging Video AudioGraphs Code
Static limitations
• Web hooks!• Long running tasks!• Indexing!• Aggregation!• Secret credentials!• Notifications
Static Add-Ons
Browser HTML CSS JS
CDN
GET /resourceX-Domain
JavaScript API
Hybrid Add-Ons
Browser HTML CSS JS
CDN
GET /resource
X-Domain JavaScript API
Service A
Service B
?
?
• Persistence • Authentication • Email / IM • 3rd party web services • Other server-side stuff
Hybrid Add-Ons
Browser
• Real-time storage & sync • Authentication • Generous free dev plan (50 connections) • Now with hosting / CDN
• Real-time storage & sync • Authentication • Self-hosted • FOSS
• Real-time storage & sync • Integration with 3rd party services:
Mailgun, Mandrill, SendGrid, Stripe, Twilio
• Generous free dev plan (30 req / sec)
Send Email
Send Email
Custom backend
Browser CDNGET /resourcespace-graph.io
POST /send-mail
Same origin policy
WebSockets
Browser CDNGET /resourcespace-graph.io
WebSockets
Browserspace-graph.io
// setup websocket & email data!var ws = new WebSocket(“myaddon.io”);!var email = {to: “[email protected]”, ..};!!// wait for connection then send data!ws.onopen(function() {! ws.send(JSON.stringify(email));!});!!// wait for confirmation!ws.onmessage(function(event) {! if (event.data === “ok”) {! AP.messages.info(“Email sent!”);! }!});
Browserspace-graph.io
CORS (Cross Origin Resource Sharing)
OPTIONS /send-mail
Access-Control-Allow-Origin: https://somecdn.com
POST /send-mail
200 OK
Static Connect Add-ons
LICENSING
STORING STUFF
ANATOMY OF A STATIC ADD-
WHAT IF I NEED A BACKEND?
• Space Graph!• Google Maps Macro!• Who's Looking v2!• Web Sequence Diagrammer v2
Example Static Add-Ons
Performance == happy users
Scalability == happy accountants
Security == happy Atlassian
Dev Loop == happy developers (you!)
Four awesome reasons