It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

React and Atlaskit in Atlassian Server/Data center apps Part 2

Part 1

Frontend

The fronted folder contains files and folders which provide UI interface for our app. Here are the contents of this folder:

Screenshot 2020-05-10 at 09.17.39.png

Let's describe all important files and folders:

package.json - an npm configuration file and contains:

  • a list of packages your project depends on
  • versions of packages that your project can use using semantic versioning rules. We have all dependencies for typescript, atlaskit, babel and so on.

.babel.rc - configuration file for Babel. Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments. We write all our code in Typescript and we need to convert Typescript code to JavaScript, so that our Atlassian Jira app could understand what we wanted to say.

webpack.config.js - configuration file for webpack. Webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles. We need to provide a single JavaScript file with all dependencies so that Jira app could produce our UI elements.

Let's have a look at the code for webpack.config.js:

const WrmPlugin = require('atlassian-webresource-webpack-plugin');
var path = require('path');


module.exports = {
    module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            use: {
                loader: "babel-loader"
            }
          }
        ]
    },
    entry: {
            'form': './src/form.js',
            'dynamictable': './src/dynamictable.js'
    },

    plugins: [
        new WrmPlugin({
            pluginKey: 'ru.matveev.alexey.atlas.jira.jira-react-atlaskit',
            locationPrefix: 'frontend/',
            xmlDescriptors: path.resolve('../backend/src/main/resources', 'META-INF', 'plugin-descriptors', 'wr-defs.xml')
        }),
    ],
    output: {
        filename: 'bundled.[name].js',
        path: path.resolve("./dist")
    }
};

As you can see from the first line we use atlassian-webresource-webpack-plugin. Why do we need this plugin?

After the webpack created a JavaScript file for us we need to include this file into the resource section of atlassian-plugin.xml file in our Jira app. We could do it manually, but it is better to automate it. Here are the setting for this plugin:

plugins: [
        new WrmPlugin({
            pluginKey: 'ru.matveev.alexey.atlas.jira.jira-react-atlaskit',
            locationPrefix: 'frontend/',
            xmlDescriptors: path.resolve('../backend/src/main/resources', 'META-INF', 'plugin-descriptors', 'wr-defs.xml')
        }),
    ],

As the result of the settings we will have a file called wr-defs.xml created in the backend/src/resources/META-INF/plugin-descriptors folder.

With locationPrefix parameter we said that JavaScript files will be placed into backend/src/resources/frontend. We will place the JavaScript files in this folder later in backend. We use this parameter here so that we could have the following line in our bundle <resource type="download" name="bundled.dynamictable.js" location="frontend/bundled.dynamictable.js"/>.

Here are the contents of the wr-defs.xml file which was generated by atlassian-webresource-webpack-plugin during packaging:

<bundles>
  <web-resource key="entrypoint-form">
    <transformation extension="js">
      <transformer key="jsI18n"/>
    </transformation>
    <context>form</context>
    <dependency>com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path</dependency>
    <resource type="download" name="bundled.form.js" location="frontend/bundled.form.js"/>
  </web-resource>
  <web-resource key="entrypoint-dynamictable">
    <transformation extension="js">
      <transformer key="jsI18n"/>
    </transformation>
    <context>dynamictable</context>
    <dependency>com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path</dependency>
    <resource type="download" name="bundled.dynamictable.js" location="frontend/bundled.dynamictable.js"/>
  </web-resource>
  <web-resource key="assets-632cdd38-e80f-4a5a-ba4c-07ba7cb36e60">
    <transformation extension="js">
      <transformer key="jsI18n"/>
    </transformation>
    <transformation extension="soy">
      <transformer key="soyTransformer"/>
      <transformer key="jsI18n"/>
    </transformation>
    <transformation extension="less">
      <transformer key="lessTransformer"/>
    </transformation>
  </web-resource>
</bundles>

As you can see we have resource sections for atlassian-plugin.xml where all our JavaScript files are defined. All we need to do is to add all these resource sections to our atlassian-plugin.xml file which is in the backend/resources folder. To accomplish it we modified the pom.xml in the backend folder:

<plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>jira-maven-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <productVersion>${jira.version}</productVersion>
                    <productDataVersion>${jira.version}</productDataVersion>
                    <compressResources>false</compressResources>
                    <enableQuickReload>true</enableQuickReload>
                    <instructions>
                        <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
                        <Export-Package></Export-Package>
                        <Import-Package>org.springframework.osgi.*;resolution:="optional", org.eclipse.gemini.blueprint.*;resolution:="optional", *</Import-Package>
                        <!-- Ensure plugin is spring powered -->
                        <Spring-Context>*</Spring-Context>
                        <Atlassian-Scan-Folders>META-INF/plugin-descriptors</Atlassian-Scan-Folders>
                    </instructions>
                </configuration>
            </plugin>

We added <Atlassian-Scan-Folders>META-INF/plugin-descriptors</Atlassian-Scan-Folders> so that during our app installation Jira would scan the META-INF/plugin-descriptors folder for additional configuration settings for atlassian-plugin.xml.

Also we added <compressResources>false</compressResources> to disable the minification of JavaScript files. All our JavaScript files are already minified.

We defined the following entry points in the webpack.config.js file:

 entry: {
            'form': './src/form.js',
            'dynamictable': './src/dynamictable.js'
    },

It means that webpack will scan ./src/form.js and ./src/dynamictable.js and create a separate JavaScript file for each file with all dependencies and put the two files in the frontend/dist folder.

./src/form.js and ./src/dynamictable.js contain nothing special. Most of the code I took from Atlaskit examples for the Form and Dynamic Table elements. Let's examine the form.js file:

import Form from "./js/components/Form";

It contains just one line to import the From class from the ./js/components/Form file. Here are the contents of ./js/components/Form

import React, { Component } from 'react';
import ReactDOM from "react-dom";
import Button from '@atlaskit/button';
import TextArea from '@atlaskit/textarea';
import TextField from '@atlaskit/textfield';
import axios from 'axios';

import Form, { Field, FormFooter } from '@atlaskit/form';

export default class MyForm extends Component {
  render() {
  return (
  <div
    style={{
      display: 'flex',
      width: '400px',
      margin: '0 auto',
      flexDirection: 'column',
    }}
  >
    <Form onSubmit={data => axios.post(document.getElementById("contextPath").value + "/plugins/servlet/form", data)}>
      {({ formProps }) => (
        <form {...formProps} name="text-fields">
          <Field name="firstname" defaultValue="" label="First name" isRequired>
            {({ fieldProps }) => <TextField {...fieldProps} />}
          </Field>

          <Field name="lastname" defaultValue="" label="Last name" isRequired>
            {({ fieldProps: { isRequired, isDisabled, ...others } }) => (
              <TextField
                disabled={isDisabled}
                required={isRequired}
                {...others}
              />
            )}
          </Field>
          <Field
            name="description"
            defaultValue=""
            label="Description"
          >
            {({ fieldProps }) => <TextArea {...fieldProps} />}
          </Field>

          <Field
            name="comments"
            defaultValue=""
            label="Additional comments"
          >
            {({ fieldProps }) => <TextArea {...fieldProps} />}
          </Field>
          <FormFooter>
            <Button type="submit" appearance="primary">
              Submit
            </Button>
          </FormFooter>
        </form>
      )}
    </Form>
  </div>
);
}
}
window.addEventListener('load', function() {
    const wrapper = document.getElementById("container");
    wrapper ? ReactDOM.render(<MyForm />, wrapper) : false;
});

All lines are taken from Atlaskit examples except these lines:

window.addEventListener('load', function() {
    const wrapper = document.getElementById("container");
    wrapper ? ReactDOM.render(<MyForm />, wrapper) : false;
});

I added these lines to show the MyForm in the div container after the page is loaded. We will define this div container later in the backend in a soy template.

Also pay attention to this line:

onSubmit={data => axios.post(document.getElementById("contextPath").value + "/plugins/servlet/form", data)}

document.getElementById("contextPath").value takes the value of the contextPath input element in our soy template. I pass to this element the context path of our application from the servlet from which I call this soy template. I will talk about it later.

So, that is it for fronted. As a result of packaging the frontend module we have two JavaScript files in the frontend/dist folder and xml bundle for atlassian-plugin.xml in the backend/src/resources/META-INF/plugin-descriptors.

Let's move to the backend.

Part 3

0 comments

Comment

Log in or Sign up to comment
TAGS

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you