How a meeting room booking system went from Flutter to Jira

Dominik Letner
Deviniti Technology-Driven Blog

--

Article created in cooperation with Artur Kruk, Software Developer at Deviniti

UPDATE: DIBS for Jira is no longer available on the Atlassian Marketplace.

Nowadays we use many tools, virtual as well as physical, in our daily work. Both kinds can be scattered and misplaced (e.g. lost passwords), so we need proper tool management to keep up with our tasks. Tools should be organized in one place and available whenever necessary. Deviniti fully supports this idea, so we enriched Jira with DIBS for Jira — a meeting room booking system that allows for sharing conference rooms, cars, and other company resources. However, DIBS for Jira was originally a Flutter-based mobile app, so introducing it to Jira was a long and complex process. If you are also thinking about importing apps built with the Flutter framework to the Jira interface, read the article where we will show you how it is done.

A meeting room booking system — the key to organized work

In companies that employ many people, proper resource management is crucial to avoid inefficiency, blunders, and other losses. Just imagine wandering with your client around the office building without being able to find a free conference room. Not to mention a situation when two employees need the same car at the same moment. Online booking systems can be fine, but they are easy to miss if they aren’t integrated with your company management system. At Deviniti, we decided to solve this problem by importing our meeting room booking system called DIBS from a mobile app to Jira. Read on to discover how we did it and what’s in it for you.

What is DIBS for Jira?

DIBS for Jira stands for a Dedicated Internal Booking System for Jira. As we said before, it works as a meeting room booking system, but also a vehicle booking system, and a resource-sharing app in general. DIBS for Jira looks like a virtual planner showing the availability of all your company resources. You can simply click a given hour and choose the ending time to book your room/vehicle/object.

DIBS — the mobile app main menu

As you know, DIBS for Jira started as a mobile app. Firstly, it was thoroughly field-tested in our own company for many months before we decided to import it as a Jira add-on. The results were more than satisfying — we had access to our booking system from Jira to stay up-to-date, and there was no more trouble with resource sharing. In addition, using DIBS for Jira internally allowed us to fix bugs and make the system more functional before releasing it to the market. How did the transition from Flutter to Jira go? Let’s analyze the entire process and see how proper app design can positively affect user experience.

DIBS for Jira — the browser app main menu
DIBS for Jira — the web app main menu

What is Flutter?

We discussed Flutter in our previous article. For the sake of clarity, here’s a little reminder. Flutter is a cross-platform framework meant for building mobile applications. It was created by Google in 2018 and mostly uses the Dart programming language. A distinct feature of this framework is Flutter widgets that allow for mimicking nativity on any operating system.

DIBS for Jira was created with Flutter in a staged and complex process to provide users with the best experience while they manage company resources. The process included designing the web app architecture and user interface. It also involved making the system Jira-compliant, multitenancy-based and filled with fluent animations thanks to proper renderers. How did this process work out? Let’s walk through it step by step.

DIBS for Jira Developer

Making our booking system work right — import to Jira

The main idea behind making DIBS for Jira an efficient conference room booking system/vehicle booking system was importing it to Jira. This couldn’t be done without proper Jira adaptation or decreasing the loading time and any related issues.

Meeting Jira requirements

All Jira add-ons must be compliant with certain requirements. Firstly, they must serve a JSON file called an app descriptor. This JSON file describes the configuration, webhooks, and all modules provided by the app. It also allows you to configure endpoints which will be called when the app is installed or uninstalled. In general, the app descriptor works as a connection between the web app and the Atlassian application. DIBS for Jira had to become compliant with this JSON file to properly function on Jira.

Secondly, the add-on needs proper authorization. Luckily, Atlassian developers provided a solution in the form of libraries that can manage common app tasks. We based our backend on the Spring Boot framework along with Java language and used the PostgreSQL database. The currently binding library for Spring Boot is atlassian-connect-spring-boot. Once we had obtained the authorization, we were ready for the next step.

Making the app load

DIBS for Jira needed to become operational on the browser version. The system provides a single general page module allowing users to book rooms, search for their reservations and view them. The URL had to be placed on the HTML page within the app descriptor. When the user selects DIBS for Jira, the app page will load and embed itself in the inline frame. The code for this is visible below. Since the app can successfully work, it’s time to focus on the graphical side.

atlassian-connect.json

{
"key": "${addon.key}",
"baseUrl": "${addon.base-url}",
"name": "DIBS for Jira",
"scopes": ["read","act_as_user"],
"enableLicensing": true,
"authentication": {
"type": "jwt"
},
"apiMigrations": {
"context-qsh": true
},
"lifecycle": {
"installed": "/installed",
"uninstalled": "/uninstalled"
},
"modules": {
"generalPages": [
{
"key": "booking",
"url": "/public/index.html",
"name": {
"value": "DIBS Bookings"
},
"icon": {
"width": 24,
"height": 24,
"url": "/public/logo.png"
}
}
]
}
}

Making the booking system look good — the User Interface

As you can see, DIBS for Jira has a very simple and intuitive design. Although company resource management sounds difficult, the entire procedure can be quite smooth. The system perfectly reflects that.

The app architecture — improving app design

As we mentioned before, Flutter is based on widgets. They describe how a specific portion of the app looks under a given state and configuration. This process is similar to those of other modern UI development frameworks such as React. The widgets compose the entire user interface, so their proper organization and functioning determine user experience. Luckily, the Flutter framework offers the opportunity to create beautiful user interfaces. To achieve that, you need a good strategy for widget development.

The widgets are rendered according to a stream made of states. These states come from events generated by the app from other immutable states as per their original core concept. It is a good idea to keep the work organized at this stage. That is why we applied separation of concerns to improve maintainability and extensibility. For that purpose, we used a Flutter-specific library called “flutter_bloc.” Consequently, we had more freedom of design which led to better widget management and improved user experience.

DIBS for Jira — the browser app user interface
DIBS for Jira — the browser app user interface

The renderers — improving app animations

Another good way to enhance user experience is introducing animations. They can serve as feedback confirming the user’s interactions around the app. The animations in Flutter come from the Material design system which is the core of every Flutter-based user interface. To apply them, you need to include the following in your pubspec.yaml file:

flutter:uses-material-design: true

For instance, the code for creating a Material confirmation button goes as follows:

Confirmation Button

import ‚package:flutter/material.dart’;
Widget _buildOkButton() {
return Container(
margin: const EdgeInsets.all(AppMargins.horizontal),
height: 60,
child: Button(
AppLocalizations.of(context).ok,
onPressed: () => Navigator.of(context).pop(),
),
);
}

The aforesaid Material buttons offer a popular ripple effect when you click them. Unfortunately, the default button animation wasn’t smooth and ruined the user experience. It required some tweaks with renderers.

DIBS for Jira — the button ripple effect
DIBS for Jira — the button ripple effect

A renderer allows for drawing the programmed user interface. Flutter offers two implemented renderers: the HTML renderer and the CanvasKit renderer. The HTML renderer is similar to other popular web frameworks like React. It outputs HTML, CSS, and SVG elements that are rendered by the browser. The CanvasKit renderer uses Canvas API to draw the user interface. The CanvasKit renderer allowed for solving the problem with animation smoothness, and thus creating a better user experience.

Making the booking system maintainable and our work easier

We have covered all the issues related to improving the user experience in DIBS for Jira. Nevertheless, we realize that some of you want to make maintaining such an app easier for your own sake. So, we are going to show you a few technicalities.

Choosing the right API — fluent communication with Jira Cloud

DIBS for Jira needs proper communication with Jira Cloud to work correctly. Two common solutions allow for achieving that: REST API published by Atlassian or JavaScript API (also known as AP). JavaScript API executes in the user’s browser, provides functions for making authorized requests to the REST API, and enables retrieving JWT tokens necessary for requests to the API of DIBS for Jira. The case is that apps written in Dart must call the JavaScript API anyway. Yet, the JS package offered by the Dart language allows for establishing proper interoperability. Let’s look at this establishment process.

Choosing the JavaScript API means including the script. This means adding a specific line of code to the module HTML page described in the atlassian-connect file (index.html to be specific). The line of code reads like this:

<script src='https://connect-cdn.atl-paas.net/all.js'></script>

The script contains a “js” dependency installed, so it’s possible to implement a bridge to the JavaScript API. DIBS for Jira simply needs to retrieve the JWT token. In terms of JavaScript, we would call AP.context.getToken to return the Promise token. The code goes as follows:

apBridge.dart

@JS('AP')
library ap;
import 'dart:async';
import 'dart:js_util';
import 'package:js/js.dart';
@JS('context.getToken')
external Object getTokenPromise();
Future<String> getToken() {
return promiseToFuture(getTokenPromise());
}

Multitenancy — managing many organizations with one server

Thanks to multitenancy in cloud computing, many organizations around the world can use the same resources. Obviously, the users aren’t aware of each other’s existence, and their data is carefully separated. When an app like DIBS for Jira goes global, multitenancy seems unavoidable. This requires introducing certain changes to the code. Here’s how it’s done.

There are several methods to achieve multitenancy. We know from experience that when dealing with single schema add-ons, the best idea is to apply a single database with separation on the table level. Here, you have an example of a resource reservation use case:

The crucial factor to maintain user data separation is a foreign key to the atlassian_host table. The key comes from atlassian-connect and contains information on the Jira Cloud instances where the app is installed. It also includes certain identification and authorization data. Each entity must have a direct or indirect relation to the respective record in this table. The identifying column is named host_client_key which is a universally unique identifier (UUID) for each Jira Cloud tenant. In the Java Persistence API (JPA) model, it is expressed as a many-to-one relationship with the reference of type AtlassianHost.

Resource.java

@Entity
(…)
public class Resource {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
(…)
@ManyToOne(optional = false)
private AtlassianHost host;
(…)
}

Entities are queried and operated upon through the JPA repository called here “ResourceRepository.” Remember that all queries should have AtlassianHost as one of their arguments.

ResourceRepository.java

public interface ResourceRepository extends JpaRepository<Resource, Long> {
Optional<Resource> findByHostAndId(AtlassianHost host, long resourceId);
List<Resource> findAllByHost(AtlassianHost host);
(…)
}

Business logic is implemented in service objects. Each method is transactional and uses repositories to retrieve or save entities. For this purpose, it needs to identify the atlassianHost which is currently being served. It uses the getHost utility method which retrieves such data from a security context. The latter is properly set in a filter provided by atlassian-spring-boot connect library. Internally, it decodes and validates the JWT token included in each request and sets SecurityContext accordingly.

@Transactional
public List<Booking> create(long resourceId, BookingRequest bookingRequest) {
Resource resource = resourceRepository.findByHostAndIdAndVisibleTrue(getHost(), resourceId).orElseThrow(() -> new ResourceNotFoundException("resource", String.valueOf(resourceId)));
Optional<BookingRepetition> bookingRepetition = optionallyCreateBookingRepetition(bookingRequest);
return bookingRepetition
.map(repetition -> createRepeatableBookings(bookingRequest, resource, repetition))
.orElseGet(() -> List.of(createSingleBooking(resource, bookingRequest.getDate(), null, bookingRequest)));
}

HostUtil.java
BookingService.java

@UtilityClass
public class HostUtil {
public static AtlassianHost getHost() {
return getHostUser()
.getHost();
}
public static AtlassianHostUser getHostUser() {
return (AtlassianHostUser) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
}
}

The frontend part communicates with the backend part through REST API. DIBS for Jira uses the spring rest controller. This controller delegates to the BookingService, and then maps the results to the data transfer object (DTO). This DTO is converted back to JSON and sent over the network.

BookingController.java

@RestController
@RequestMapping("/api/v1")
(…)
public class BookingController {
private final BookingService bookingService;
private final BookingMapper bookingMapper;
@PostMapping("/resource/{resourceId}/booking")
public List<BookingResponse> create(
@PathVariable("resourceId") Long resourceId,
@Valid @RequestBody BookingRequest bookingRequest) {
return bookingMapper.mapEntitiesToBookingResponse(
bookingService.create(resourceId, bookingRequest)
);
}

Thanks to these operations, we have successfully implemented multitenancy. As a result, server maintenance is now simplified while user data remains properly separated.

DIBS for Jira — ready for the market

We walked you through the entire process of turning an app built with the Flutter framework into a Jira add-on. To summarize, you can come up with a good app idea by analyzing and fulfilling your own business needs. What is more, always remember to make the app look and work well for your users while keeping its maintainability as easy as possible. These two features are hard to get, but you can find the right method if you search long enough (or follow our example).

So, if you need a meeting room booking system that can also work as a vehicle booking system or a booking system for other company resources, you can try out DIBS for Jira. Remember that tool accessibility is crucial nowadays, so keep your tools organized. If you are willing to adapt your Flutter-based app to Jira, you can always contact us for support. Feel free to read our other articles related to Flutter-based app development:

  1. Flutter for web & mobile development — Things you need to know
  2. 6 small things that let you become a better Flutter developer
  3. Peak Flutter performance, or How to bring your app to 60 fps

--

--