Bereaksi dan Atlaskit di server Atlassian dan plugin pusat data

Halo semuanya!

Pada artikel ini, saya ingin berbicara tentang cara menggunakan React dan Atlaskit di plugin Atlassian di lingkungan Server dan Data Center.

pengantar


Saat ini, jika Anda mengembangkan plugin untuk produk Atlassian untuk Server dan Pusat Data, maka vm , soy , requireejs , jquery , backbone tersedia bagi Anda untuk mengembangkan antarmuka pengguna dari kotak . Di sini di sini Anda dapat membaca artikel saya tentang cara menggunakan pustaka yang tersedia di luar kotak.

Tumpukan teknologi ini sudah ketinggalan zaman dan ingin menggunakan sesuatu yang lebih baru. Saya memilih naskah dan bereaksi sebagai tumpukan . Juga, untuk membuatnya lebih mudah untuk mentransfer plug-in server ke cloud, saya memilih pustaka elemen antarmuka pengguna atlaskit .

Pada artikel ini saya akan berbicara tentang Atlassian Jira, tetapi pendekatan yang sama dapat digunakan untuk produk server dan pusat data Atlassian lainnya.

Untuk mereproduksi contoh-contoh dari artikel ini, Anda harus menginstal git dan Atlassian SDK .

Jadi, mari kita mulai!

Instal arketipe Maven dan buat proyek baru.


Saya membuat arketipe Maven untuk membuatnya lebih mudah untuk membuat proyek baru yang sudah akan berisi semua pengaturan yang diperlukan untuk membuat plugin dengan React dan Atlaskit.

Jika Anda tidak ingin menggunakan arketipe, Anda dapat mengambil plug-in yang sudah dibuat dari arketipe ini dan pergi ke bagian Kami mengumpulkan dan meluncurkan proyek.

Kloning arketipe dari repositori Bitbucket saya:

git clone https://alex1mmm@bitbucket.org/alex1mmm/jira-react-atlaskit-archetype.git --branch v1 --single-branch

Buka folder folder jira-react-atlaskit-archetype dan instal arketipe ini di repositori Maven lokal Anda:

cd jira-react-atlaskit-archetype
atlas-mvn install

Setelah itu, buka folder satu tingkat lebih tinggi dan buat proyek baru berdasarkan pola dasar ini:

cd ..
atlas-mvn archetype:generate -DarchetypeCatalog=local

Sebuah pertanyaan akan diminta untuk memilih pola dasar yang diperlukan. Ini adalah arketipe saya di repositori lokal:

1: local -> com.cprime.jira.sil.extension:sil-extension-archetype (This is the com.cprime.jira.sil.extension:sil-extension plugin for Atlassian JIRA.)
2: local -> ru.matveev.alexey.sil.extension:sil-extension-archetype (This is the ru.matveev.alexey.sil.extension:sil-extension plugin for Atlassian JIRA.)
3: local -> ru.matveev.alexey.atlas.jira:jira-react-atlaskit-archetype-archetype (jira-react-atlaskit-archetype-archetype)

Saya perlu memilih ru.matveev.alexey.atlas.jira: jira-react-atlaskit-archetype-archetype, jadi saya menunjukkan angka 3 sebagai jawabannya.

Maka Anda perlu menentukan groupid dan artifactid untuk proyek baru:

Define value for property 'groupId': react.atlaskit.tutorial
Define value for property 'artifactId': my-tutorial
Define value for property 'version' 1.0-SNAPSHOT: : 
Define value for property 'package' react.atlaskit.tutorial: : 
Confirm properties configuration:
groupId: react.atlaskit.tutorial
artifactId: my-tutorial
version: 1.0-SNAPSHOT
package: react.atlaskit.tutorial
 Y: : Y

Kami merakit dan menginstal proyek


Dalam kasus saya, proyek baru ada di folder tutorial saya. Ayo buka folder ini dan bangun proyek:

cd my-tutorial
atlas-mvn package

Setelah proyek selesai, buka folder backend dan jalankan Atlassian Jira:

cd backend
atlas-run

Plugin uji


Setelah Atlassian Jira dimulai, buka browser di alamat berikut:

http://localhost:2990/jira

Anda harus masuk sebagai admin: admin dan pergi gigi -> Kelola aplikasi.



Anda akan melihat menu dari plugin kami. Tetapi sebelum memulai elemen Atlaskit kami, buka Sistem -> Logging dan Profiling dan atur level logging INFO untuk paket react.atlaskit.tutorial.servlet.



Sekarang kembali ke Kelola aplikasi dan klik pada menu Formulir. Kita akan melihat formulir entri data yang ditampilkan menggunakan elemen Formulir Atlaskit :



Isi semua bidang teks dan klik tombol Kirim:



Sekarang, jika Anda membuka file atlassian-jira.log, Anda akan melihat sesuatu seperti ini:

2020-05-10 08:44:29,701+0300 http-nio-2990-exec-4 INFO admin 524x12509x1 15awhw2 0:0:0:0:0:0:0:1 /plugins/servlet/form [r.a.tutorial.servlet.FormServlet] Post Data: {"firstname":"Alexey","lastname":"Matveev","description":"No description","comments":"and no comments"}

Ini berarti bahwa dengan mengklik tombol Kirim, data kami berhasil ditransfer ke servlet yang melayani formulir ini, dan servlet ini menampilkan data yang dimasukkan dalam file log.
Sekarang mari kita pilih menu Dynamic Table. Anda akan melihat elemen Atlaskit Dynamic Table : Hanya



itu yang dilakukan plugin kami. Sekarang mari kita lihat bagaimana semuanya bekerja!

Plugin di dalam


Berikut adalah struktur plugin kami:



backend berisi plugin Atlassian Jira yang dibuat menggunakan Atlassian SDK.
frontend berisi elemen UI yang akan digunakan oleh plugin kami.
pom.xml file pom di mana dua modul didefinisikan:

    <modules>  
        <module>frontend</module>
        <module>backend</module>
    </modules>

Sekarang mari kita lihat folder yang digawangi.

Paling depan


Folder frontend berisi file-file berikut: Saya akan



menjelaskan file-file utama.

package.json adalah file konfigurasi untuk npm yang berisi informasi berikut:

  • daftar paket yang bergantung pada proyek kami.
  • versi paket yang kami gunakan.

Kami akan menggunakan paket seperti naskah, atlaskit, babel dan lainnya.

.babel.rc - file konfigurasi untuk Babel . Babel digunakan untuk menerjemahkan kode ECMAScript 2015+ menjadi kode JavaScript. Kami akan menulis kode skrip kami, jadi kami harus menerjemahkannya ke dalam kode JavaScript agar plugin Jira dapat bekerja dengannya.

webpack.config.js - file konfigurasi untuk webpack . Webpack memproses aplikasi kita, membuat grafik dependensi dan menghasilkan bundel yang berisi semua JavaScript yang diperlukan agar aplikasi kita berfungsi. Agar plugin Jira berfungsi dengan JavaScript kami, kami memerlukan satu file Javascript untuk setiap titik masuk. Dalam kasus kami, titik masuk adalah item menu Formulir dan Tabel Dinamis.

Berikut ini isi file 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")
    }
};

Seperti yang Anda lihat, kami menggunakan atlassian-webresource-webpack-plugin .

Diperlukan agar setelah webpack membuat file JavaScript, file ini akan secara otomatis ditambahkan sebagai sumber daya web:

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')
        }),
    ],

Sebagai hasil dari konfigurasi ini, setelah merakit modul frontend, file wr-defs.xml akan dibuat di folder backend / src / resources / META-INF / plugin-descriptors.

Parameter locationPrefix memungkinkan kita menentukan lokasi file JavaScript di plugin Jira. Dalam kasus kami, kami menunjukkan bahwa file akan berlokasi di folder backend / src / resources / frontend. Kami akan meletakkan file JavaScript di folder ini nanti di modul backend, tetapi sekarang parameter ini memungkinkan kami untuk mendapatkan baris seperti itu di file wr-defs.xml <resource type = "unduh" name = "bundled.dynamictable.js" location = "frontend / bundled.dynamictable.js ยป />.

Berikut adalah isi dari file wr-defs.xml yang telah dibuat selama proses pembangunan proyek:

<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>

Seperti yang Anda lihat, kami memiliki bagian sumber daya web tambahan di mana file JavaScript yang dibuat oleh webpack didefinisikan. Yang tersisa bagi kami adalah memberi tahu Jira, sehingga saat memasang plugin kami, kami juga menggunakan sumber daya web dari folder backend / src / resources / META-INF / plugin-descriptor. Untuk melakukan ini, kami membuat perubahan berikut pada file backend / pom.xml:

<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>

Kami telah menambahkan <Atlassian-Scan-Folders> META-INF / plugin-descriptors </Atlassian-Scan-Folders>. Parameter ini akan memberi tahu Jira bahwa perlu mencari sumber daya web tambahan di folder META-INF / plugin-descriptors.

Kami juga menambahkan <compressResources> false </compressResources> untuk menonaktifkan minifikasi file JavaScript kami. Mereka sudah diperkecil.
Kami juga menetapkan dua titik masuk ke aplikasi kami di file webpack.config.js:

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

Ini berarti bahwa webpack akan memindai file ./src/form.js dan ./src/dynamictable.js dan membuat dua file JavaScript, yang masing-masing merupakan file untuk salah satu titik masuk. File-file ini akan dibuat di folder frontend / dist.

./src/form.js dan ./src/dynamictable.js tidak mengandung sesuatu yang istimewa. Saya mengambil sebagian besar kode dari contoh di Atlaskit.

Berikut ini isi file form.js:

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

Berikut ini hanya satu baris yang mengimpor kelas dari file ./js/components/Form.js.

Berikut ini isi file ./js/components/Form.js:

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;
});</code></pre>
<!-- /wp:code -->       :<!-- wp:code -->
<pre class="wp-block-code"><code>window.addEventListener('load', function() {
    const wrapper = document.getElementById("container");
    wrapper ? ReactDOM.render(<MyForm />, wrapper) : false;
});

Di sini saya menunjukkan komponen MyForm dalam wadah div. Wadah ini akan ditentukan dalam templat plugin Jira kedelai.

Perhatikan juga baris ini:

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

nilai document.getElementById ("contextPath"). mendapatkan nilai bidang dengan id contextPath. Saya mendefinisikan bidang ini di templat kedelai di plugin Jira. Nilai dalam bidang ini berasal dari servlet yang item menu Formulir terikat. Dalam kasus saya, contextPath berisi nilai / jira, karena ketika memulai Jira dari Atlassian SDK, jalur konteks ini ditetapkan.

Dan ini semua tentang frontend. Sebagai hasil dari perakitan modul frontend, kami mendapatkan dua file JavaScript di folder frontend / dist dan xml dengan sumber daya web tambahan di folder backend / src / resources / META-INF / plugin-deskriptor.

Sekarang mari kita beralih ke backend.

Backend


Saya menambahkan plugin ini ke file backend / pom.xml:

<plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>properties-maven-plugin</artifactId>
                <version>1.0.0</version>
                <executions>
                    <execution>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>write-project-properties</goal>
                        </goals>
                        <configuration>
                            <outputFile>${project.build.outputDirectory}/maven.properties</outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <id>copy frontend files to resources</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>src/main/resources/frontend</outputDirectory>
                            <overwrite>true</overwrite>
                            <resources>
                                <resource>
                                    <directory>../frontend/dist</directory>
                                    <includes>
                                        <include>*.*</include>
                                    </includes>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

properties-maven-plugin membuat file maven.properties yang berisi semua properti Maven. Saya memerlukan properti atlassian.plugin.key untuk memanggil sumber daya web dari servlets yang terikat ke item menu plugin kami.

maven-resources-plugin mengambil file JavaScript dari folder frontend / dist dan menyalinnya ke folder backend / resources / frontend.

Kemudian saya membuat item menu dan membuat panggilan ke servlets dari item menu ini.

Inilah baris-baris dari file atlassian-plugin.xml:

<servlet name="Form Servlet" i18n-name-key="form-servlet.name" key="form-servlet" class="react.atlaskit.tutorial.servlet.FormServlet"> 
    <description key="form-servlet.description">The Form Servlet Plugin</description>  
    <url-pattern>/form</url-pattern>
  </servlet>  
  <servlet name="Dynamic Table Servlet" i18n-name-key="dynamic-table-servlet.name" key="dynamic-table-servlet" class="react.atlaskit.tutorial.servlet.DynamicTableServlet"> 
    <description key="dynamic-table-servlet.description">The Dynamic Table Servlet Plugin</description>  
    <url-pattern>/dynamictable</url-pattern>
  </servlet>
  <web-section name="React Plugin" i18n-name-key="react.name" key="react" location="admin_plugins_menu" weight="1000">
    <description key="react.description">React Plugin</description>
    <label key="react.label"/>
  </web-section>
  <web-item name="from web item" i18n-name-key="form.name" key="form" section="admin_plugins_menu/react" weight="1000">
    <description key="form.description">Form</description>
    <label key="form.label"/>
    <link linkId="configuration-link">/plugins/servlet/form</link>
  </web-item>
  <web-item name="dynamic table web item" i18n-name-key="dynamictable.name" key="dynamictable" section="admin_plugins_menu/react" weight="1000">
    <description key="dynamictable.description">Dynamic Table</description>
    <label key="dynamictable.label"/>
    <link linkId="configuration-link">/plugins/servlet/dynamictable</link>
  </web-item>

Jadi, kami memiliki menu dan servlet yang dipanggil dari item menu ini.
Sekarang mari kita lihat servlets:

FormServlet.java:

public class FormServlet extends HttpServlet{
    private static final Logger log = LoggerFactory.getLogger(FormServlet.class);
    private final ResourceService resourceService;
    private final SoyTemplateRenderer soyTemplateRenderer;

    public FormServlet(@ComponentImport  SoyTemplateRenderer soyTemplateRenderer, ResourceService resourceService) {
        this.resourceService = resourceService;
        this.soyTemplateRenderer = soyTemplateRenderer;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        String pluginKey = this.resourceService.getProperty("atlassian.plugin.key");
        Map<String, Object> map = new HashMap<>();
        map.put("contextPath", req.getContextPath());

        String html = soyTemplateRenderer.render(pluginKey + ":jira-react-atlaskit-resources", "servlet.ui.form", map);

        resp.setContentType("text/html");
        resp.getWriter().write(html);
        resp.getWriter().close();    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StringBuffer jb = new StringBuffer();
        String line = null;
        try {
            BufferedReader reader = req.getReader();
            while ((line = reader.readLine()) != null)
                jb.append(line);
        } catch (Exception e) { /*report an error*/ }
        log.info(String.format("Post Data: %s", jb.toString()));

        String pluginKey = this.resourceService.getProperty("atlassian.plugin.key");
        Map<String, Object> map = new HashMap<>();
        map.put("contextPath", req.getContextPath());

        String html = soyTemplateRenderer.render(pluginKey + ":jira-react-atlaskit-resources", "servlet.ui.form", map);

        resp.setContentType("text/html");
        resp.getWriter().write(html);
        resp.getWriter().close();
    }}

Saya mendefinisikan dua variabel resourceService dan soyTemplateRenderer dan menginisialisasi variabel-variabel ini di konstruktor kelas. resourceService - kacang yang membaca properti dari file maven.properties. soyTemplateRenderer - Jira bean yang dapat memanggil templat kedelai.
Dalam metode doGet, saya mendapatkan nilai properti atlassian.plugin.key dan path konteks. Lalu saya melewati path konteks sebagai parameter ke templat kedelai dan memanggil templat kedelai dengan nama servlet.ui.form.

Berikut adalah isi dari file kedelai:

{namespace servlet.ui}
/**
 * This template is needed to draw the form page.
 */
{template .form}
    {@param contextPath: string}
    {webResourceManager_requireResourcesForContext('form')}
    <html>
    <head>
        <meta charset="utf-8"/>
        <meta name="decorator" content="atl.admin">
        <meta name="admin.active.section" content="admin_plugins_menu/react">
        <title>Form Page</title>
    </head>
    <body>
    <div class="field-group hidden">
        <input class="text" type="text" id="contextPath" name="contextPath" value="{$contextPath}">
    </div>
    <div id="maincontainer">
        <div id="container">
        </div>
    </div>
    </body>
    </html>
{/template}
/**
 * This template is needed to draw the dynamic table page.
 */
{template .dynamictable}
    {webResourceManager_requireResourcesForContext('dynamictable')}
    <html>
    <head>
        <meta charset="utf-8"/>
        <meta name="decorator" content="atl.admin">
        <meta name="admin.active.section" content="admin_plugins_menu/react">
        <title>Dynamic Table Page</title>
    </head>
    <body>
    <div id="maincontainer">
        <div id="container">
        </div>
    </div>
    </body>
    </html>
{/template}

Kode untuk template cukup sederhana. Saya memanggil sumber daya web untuk item menu dan membuat div kontainer yang akan digunakan oleh React.

Saya mendaftarkan file kedelai itu sendiri di atlassian-plugin.xml:

<web-resource key="jira-react-atlaskit-resources" name="jira-react-atlaskit Web Resources"> 
    ...
    <resource type="soy" name="soyui" location="/templates/servlets.soy"/>
    ...
    <context>jira-react-atlaskit</context> 
  </web-resource> 

Itu semua yang perlu Anda lakukan untuk menggunakan Bereaksi dan Atlaskit di server Atlassian dan plugin pusat data.

All Articles