instantly connect people everywhere to what’s most meaningful to them.
AtlasCamp 2015: Connect everywhere - Cloud and Server
-
Upload
atlassian -
Category
Technology
-
view
487 -
download
1
Transcript of AtlasCamp 2015: Connect everywhere - Cloud and Server
Connect Add-ons for Cloud & Server
PATRICK STREULE • ARCHITECT • ATLASSIAN • @PSTREULE
Connect
Overview
Cloud
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JS
Add-On UIAdd-On JS
AssetsAPI
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JS
Add-On UIAdd-On JS
AssetsAPI
Cloud Create iFrame, XDM bridge
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JS
Add-On UIAdd-On JS
AssetsAPI
CloudExpose
Host API
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JS
Add-On UIAdd-On JS
AssetsAPI
CloudLifecycle, Webhooks
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JS
Add-On UIAdd-On JS
AssetsAPI
Cloud
REST API
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JS
Add-On UIAdd-On JS
AssetsAPI
REST API Bridge
Cloud
Now in Server
Server
Product – Server v1Product – Server v2
Browser
Add-On Service
?
Add-On UIAdd-On JS
AssetsAPI
Product – Server v3
?
Server
Product – Server v1Product – Server v2
Browser
Add-On Service
?
Add-On UIAdd-On JS
AssetsAPI
Product – Server v3
?
iFrame setup? XDM?
API Version?
Server
Product – Server v1Product – Server v2
Browser
Add-On Service
?
Add-On UIAdd-On JS
AssetsAPI
Product – Server v3
?
Auth?Privacy?
Connectivity?
Server
Product – Server v1Product – Server v2
Browser
Add-On Service
?
Add-On UIAdd-On JS
AssetsAPI
Product – Server v3
?
Connectivity? API Version?
Auth?
Example Sequence Diagram Add-On
• Purely client-side• No server-side rendering• Javascript & SVG
• No data stored by the add-on• Uses macro body for storage• Uses REST API to fetch the macro
body• However: The Connect version
stores the installation payload
Browser
Product – Cloud Add-On Service
Connect Plugin
Connect JSAdd-On UI
Add-On JS
Assets
Design Choices
Con
nect
P2
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
Assets
Connect JS Shim
Show me code
Plugin XML <web-resource key="sequence-diagram-resources" name="sequence-diagram Web Resources"> <resource type="download" name="blueprint.css" location="/css/blueprint.css"/> <resource type="download" name="confluence.css" location="/css/confluence.css"/> <resource type="download" name="napkin.css" location="/css/napkin.css"/> <resource type="download" name="plain.css" location="/css/plain.css"/> <resource type="download" name="sequence-diagrams.js" location="/js/sequence-diagrams.743cf0e5.js"/> <resource type="download" name="connect-api.js" location="/js/connect-api.js"/> <resource type="download" name="addon.js" location="/js/addon.js"/> </web-resource>
<servlet name="Diagram IFrame Servlet" key="sequence-diagram-servlet" class="com.pstreule.SequenceDiagramIFrame"> <url-pattern>/sequence-diagram</url-pattern> </servlet>
<xhtml-macro name="sequence-diagram" class="com.pstreule.SequenceDiagramMacro" key="sequence-diagram-macro"> <parameters> <parameter name="theme" type="enum" required="true"> <value name="Confluence"/> <value name="Blueprint"/> <value name="Napkin"/> <value name="Plain"/> </parameter> <parameter name="width" type="string"/> </parameters> </xhtml-macro>
Render the iFrame public SequenceDiagramMacro(TemplateRenderer renderer, ApplicationProperties applicationProperties) { this.renderer = renderer; this.applicationProperties = applicationProperties; }
@Override public String execute(Map<String, String> parameters, String storageFormatBody, ConversionContext conversionContext)
throws MacroExecutionException { ContentEntityObject entity = conversionContext.getEntity(); String baseUrl = applicationProperties.getBaseUrl();
URI uri = UriBuilder.fromPath(baseUrl + "/plugins/servlet/sequence-diagram") .queryParam("pid", entity.getIdAsString()) .queryParam("pv", entity.getVersion()) .queryParam("mh", DigestUtils.md5Hex(storageFormatBody)) .queryParam("theme", parameters.get("theme")) .build();
Map<String, Object> context = new HashMap<>(); context.put("url", uri.toString()); context.put("name", "preview".equals(conversionContext.getOutputType()) ? storageFormatBody : "");
return render("/templates/iframe.vm", context); }
<iframe src="$url" name="$name" frameborder="0" rel="nofollow" scrolling="no"></iframe>
Render the iFrame Contentpublic class SequenceDiagramIFrame extends HttpServlet { private final TemplateRenderer renderer; private final PageBuilderService pageBuilderService;
public SequenceDiagramIFrame(TemplateRenderer renderer, PageBuilderService pageBuilderService) { this.renderer = renderer; this.pageBuilderService = pageBuilderService; }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("text/html");
StringWriter writer = new StringWriter(); WebResourceAssembler assembler = pageBuilderService.assembler(); assembler.assembled().drainIncludedResources(); assembler.resources().requireWebResource("com.pstreule.sequence-diagram:sequence-diagram-resources"); WebResourceSet set = assembler.assembled().drainIncludedResources(); set.writeHtmlTags(writer, UrlMode.RELATIVE);
renderer.render("/templates/sequence-diagram.vm", Collections.singletonMap("resourcesWithHtml", writer.toString()), resp.getWriter()); } }
<html> <head> $resourcesWithHtml </head> <body> <div id="sequence-diagram"></div> <script type="application/javascript"> renderSequenceDiagram("sequence-diagram", window.name) </script> </body> </html>
Add-On JavaScript Codefunction renderSequenceDiagram(id, previewMacroBody) { function render(diagramCode) { function onRenderSuccess(dimensions) { AP.resize(dimensions.w + 10, dimensions.h + 10); } var themeName = Loader.getUrlParameter("theme"); var theme = SequenceDiagramThemes.byName(themeName); var controller = new SequenceDiagramController(id); controller.renderSequenceDiagram(diagramCode, theme, null, onRenderSuccess, onRenderError); }
function loadMacroAndRender() { function onLoadSuccess(responseBody) { var diagramCode = JSON.parse(responseBody).body; render(diagramCode); } AP.require(['request'], function (request) { var pageId = Loader.getUrlParameter("pid"); var pageVersion = Loader.getUrlParameter("pv"); var macroHash = Loader.getUrlParameter("mh"); request({ url: "/rest/api/content/" + pageId + "/history/" + pageVersion + "/macro/hash/" + macroHash, success: onLoadSuccess }); }); } loadMacroAndRender(); }
Connect JS Shimvar AP = {}; (function (AP) {
var AJS = window.parent.AJS; var $ = AJS.$;
_modules = { 'request': function (url, args) { … $.ajax({ url: url, … }).then(done, fail); } };
AP.require = function (modules, callback) { var requiredModules = Array.isArray(modules) ? modules : [modules]; var args = requiredModules.map(function (module) { return _modules[module]; }); callback.apply(window, args); };
AP.resize = function (width, height) { AJS.$(window.frameElement).css({width: width, height: height}); };
})(AP);
Best Practices
Make it Self-Contained
Run in Process
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
AssetsAPI
Connect JS Shim
Adapter
Run in Process
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
AssetsAPI
Connect JS Shim
Adapter
No cross-domain bridge needed
Run in Process
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
AssetsAPI
Connect JS Shim
AdapterEvent Handling, Storage
Run in Process
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
AssetsAPI
Connect JS Shim
Adapter
100% Reusable
100% Reusable
Run in Process
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
AssetsAPI
Connect JS Shim
Adapter
100% reusable (maybe)
Use Client-Side Rendering as much
as Possible
Shrink the server-side
Browser
Product – ServerP2 Plugin
Add-On UIAdd-On JS
AssetsAPI
Connect JS Shim
Use In-Product Data Storage
Use Entity Properties for Data Storage
POST /rest/api/content/{content_ID}/property Content-Type: application/json
{ "key" : "attachment", "value" : { "size": 234374, "description": "My description" ... } }
GET /rest/api/content/{content_ID}/propertyGET /rest/api/2/issue/{key}/properties/
PUT /rest/api/2/issue/{key}/properties/attachment Content-Type: application/json
{ "size": 234374, "description": "My description" ... }
Use Entity Properties for Data Storage{ "jiraEntityProperties": [ { "keyConfigurations": [ { "propertyKey": "attachment", "extractions": [ { "objectName": "attachment.size", "type": "number", "alias": "attachmentSize" } ] } ], "entityType": "issue", "name": { "value": "Attachment Index Document" } } ] }
{ "confluenceContentProperties": [ { "keyConfigurations": [ { "propertyKey": "attachment", "extractions": [ { "objectName": "attachment.size", "type": "number" } ] } ], "name": { "value": "Attachment Index Document" } } ] }
Use client-side REST Calls
// Get the content properties of an issue AP.require(['request'], function(request){ request({ url: '/rest/api/2/issue/' + issueKey + '/properties/', success: function(responseText){ var properties = JSON.parse(responseText); … } }); });
Expose REST API for Server-Side
Logic
JAX-RS/Jersey
@Path("my-resource") public class MyResource { @GET @Produces("application/json") public Response getSomething(@Context HttpServletRequest request) { // ... return Response.ok(); } }
Tools**currently in planning
Thank you!
PATRICK STREULE • ARCHITECT • ATLASSIAN • @PSTREULE
Connect everywhere - Cloud and Server
Submit your feedback: go.atlassian.com/acconnectcloud