Building Realtime Add-ons with Firebase and Atlassian Connect


Ossama Alami

VP, Developer Happiness at Firebase
+OssamaAlami
@ossamaalami

Agenda

  • What is Firebase?
  • Building Serverless Add-ons
  • Collaborative Planning Poker with Jira Integration
  • Security & Authorization
  • Deploying
  • Resources

What is Firebase?


Firebase is a platform for building realtime apps without a backend.


  • Realtime Database store and sync data across clients
  • Security & Authentication easily authenticate users
  • Hosting production-grade static asset hosting

Realtime Database

  • NoSQL Database
  • JSON Objects
  • Each Piece of Data has it's own URL
  • Clients are notified when changes happen at a specific node

Realtime Database

Create a Reference


var ref = new Firebase("https://atlassian14.firebaseio.com/");
      

Write Data to Firebase


ref.set("Hello Atlassian Connect!");
      

Read Data from Firebase


ref.on("value", function (snapshot) {
  var data = snapshot.val();
  console.log(data);
});

Realtime Database

Platform Support


Realtime Database

Framework Support

Security & Authentication

  • Control access and validate data
  • Expression-based rules language enforced by Firebase
  • One line integration with Facebook, Twitter, Google, GitHub auth
  • Easy custom auth integration

Firebase Hosting

  • Globally backed by a CDN
  • All sites are served over HTTPS/SSL
  • One command deploys, one click rollbacks


Demo

View in Firebase Dashboard

More About Firebase

  • 95,000 registered developers
  • Founded in 2011 in SF
  • Customers include: Citrix, Atlassian, CBS, Twitch, Warby Parker, Coffee Meets Bagel, Instacart, Yahoo
  • One million+ concurrents
  • Billions of operations per week

Building Serverless Add-ons

Atlassian Connect JavaScript API

  • Lives in an IFRAME in Jira or Confluence
  • Load the Atlassian Connect library directly from your instance
  • Creates a cross-domain messaging bridge
  • AP class allows you to interact

<script src="https://{OnDemand hostname}/{context}
  /atlassian-connect/all.js"></script>
      

AP.getUser(callback)  // Gets the user's profile information
      

Firebase + Atlassian Connect

Realtime Integrated Whiteboard in Jira in 5 minutes

Atlassian Connect Configuration

atlassian-connect.json


{
    "name": "Whiteboard",
    "description": "Firebase Atlassian Connect Whiteboard add-on",
    "key": "com.firebase.example.whiteboard",
    "baseUrl": "https://at-whiteboard.firebaseapp.com",
    "vendor": {
        "name": "Firebase, Inc.",
        "url": "https://firebase.com"
    },
    "authentication": {
        "type": "none"
    },
    "scopes": [ "READ" ],
    "modules": {
        "generalPages": [
            {
                "url": "/whiteboard.html",
                "key": "whiteboard",
                "location": "system.top.navigation.bar",
                "name": {
                    "value": "Whiteboard"
                }
            }
        ]
    }
}
      

Atlassian Connect JavaScript API

Our App's HTML + JavaScript


<script src="https://cdn.firebase.com/js/client/1.0.21/firebase.js"></script>
<script src="http://localhost:2990/jira/atlassian-connect/all.js" type="text/javascript"></script>
...

<script>
AP.getUser(function(user) {
    console.log(user);
    setup(user.fullName);
});
...
function setup(userName) {
  ...
}

</script>

Whiteboard Demo

Data Integration with Connect

Make requests using the Connect REST API using the helper method AP.require('request',...)


// Get a list of JIRA dashboards using the JIRA REST API.
AP.require('request', function(request){
  request({
    url: '/rest/api/2/dashboard',
    success: function(response){
      console.log(response);
    },
    error: function(error) {
      console.log(error);
    }
  });
});
      

Collaborative Planning Poker with Jira Integration

Planning Poker

  • Consensus-based technique for estimating the effort or size of projects
  • Play number cards face-down, reveal and discuss
  • Eliminates anchoring effect in estimating project effort

FirePoker.io

Poker + Jira Integration


  • Populate it with Jira issues
  • Pull the estimate data back into Jira
  • Do it all without a server

Poker + Jira Integration


  • First, a Demo
  • All Javascript + HTML
  • Hosted locally

Integration

atlassian-connect.json


{
    "name": "Firepoker",
    "description": "Firebase Atlassian Connect Firepoker add-on",
    "key": "com.firebase.example.firepoker",
    "baseUrl": "http://localhost:8000",
    "vendor": {
        "name": "Firebase, Inc.",
        "url": "https://firebase.com"
    },
    "authentication": {
        "type": "none"
    },
    "scopes": [ "READ", "WRITE" ],
    "modules": {
        "jiraProjectTabPanels": [
            {
                "url": "/index.html?projectId={project.id}",
                "weight": 100,
                "key": "index",
                "name": {
                   "value": "Play Firepoker"
                }
            }
        ]
    }
}

Integration

JavaScript


var ref = new Firebase("https://at-poker.firebaseio.com/games");
var currentQueryRef = null;
var fbIssues = [];

$(document).ready(function() {
  var projectId = $.QueryString['projectId'];
  var jql = 'project = '+projectId+' AND resolution = Unresolved AND status != Closed AND issuetype = "New Feature" ORDER BY priority DESC'

  $('#jql').val(jql);

  $('#search').click(searchJira);
  $('#send').click(sendToFirepoker);

  populateGameList();
});

//Populate the game list from FirePoker
function populateGameList() {
  ref.on('child_added', function(snapshot) {
    var game = snapshot.val();
    var gameId = snapshot.name();
    var gameName = game.name;
    var li = $('<li>').text(game.name).click(function() {
      $('#gameId').val(gameId);
    })

    li.appendTo('#available_games');

  });
}

//Search Jira for issues
function searchJira() {
  $('#results').text('...');
  var jql = $('#jql').val();

  AP.require('request', function(request){
    request({
      url: '/rest/api/2/search?jql=' + encodeURIComponent(jql),
      success: function(response){
        var issues = $.parseJSON(response).issues;

        fbIssues = [];

        //format the results for FirePoker
        $('#results').text('');
        for (var i = issues.length - 1; i >= 0; i--) {
          fbIssues.push({
            endedAt: false,
            points: 0,
            results: false,
            startedAt: false,
            status: "queue",
            title: issues[i].fields.summary,
            notes: issues[i].key
          });

          $('<p id='+issues[i].key+'>').append('<a target="_blank" href="http://localhost:2990/jira/browse/'+issues[i].key+'">'+issues[i].key+'</a>').append(' ' + issues[i].fields.summary).appendTo("#results");
        }
      },
      error: function(error) {
        console.log(error);
      }
    });
  });
}

//Send the stories to FirePoker
function sendToFirepoker() {
  if(currentQueryRef != null) {
    currentQueryRef.off('child_changed');
  }

  var currentGame = $("#gameId").val();

  //https://at-poker.firebaseio.com/games/[game-id]/stories
  var storiesRef = ref.child(currentGame).child('stories');

  storiesRef.set(fbIssues);

  //check to see if a value has been saved and update the issue
  storiesRef.on('child_changed', function(snapshot) {
    var story = snapshot.val();

    //if the story is closed, update the jira issue
    if(story.status === 'closed') {
      $('#' + story.notes).append(' (' +story.points + ')');

      AP.require('request', function(request){
        request({
          url: '/rest/api/2/issue/' + encodeURIComponent(story.notes),
          type: 'PUT',
          contentType: 'application/json',
          data: '{"fields": {"customfield_10100": '+story.points+'}}',
          success: function(response){
            console.log(response);
          },
          error: function(error) {
            console.log(error);
          }
        });
      });
    }
  });

  currentQueryRef = storiesRef;
}

Security & Authorization

Firebase Security Rules


  • Expression based rules language with a JavaScript syntax
  • Control read and write access to your data
  • Validation rules that validate written data
  • Control access based on user with an auth Variable

Authenticating Users


var myRef = new Firebase("https://YOUR-FIREBASE.firebaseio.com");
var authClient = new FirebaseSimpleLogin(myRef, function(error, user) {
  if (error) {
    // an error occurred while attempting login
    console.log(error);
  } else if (user) {
    // user authenticated with Firebase
    console.log("User ID: " + user.uid + ", Provider: " + user.provider);
  } else {
    // user is logged out
  }
});

authClient.login('google');
      

Example Security Rules


{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid",

        "email": {
          // an email is only allowed in the profile if it matches
          // the auth token's email account (for Google or password auth)
          ".validate": "newData.val() === auth.email"
        }
      }
    }
  }
}
      

Deploying

Firebase Hosting


Now that we've built an add-on with just frontend code we still need a place to host it.


  • Install the Firebase command line tools using npm
  • Run firebase init
  • Run firebase deploy
  • That's it!

Firebase Hosting


Our app is deployed at
https://<firebase-name>.firebaseapp.com


  • Securly served via SSL/HTTPS
  • Deployed on a Global CDN
  • Use your own custom domain

Resources

Resources



Thank You!