Reagir e Atlaskit em plugins de servidor e data center Atlassian

Olá a todos!

Neste artigo, eu gostaria de falar sobre como usar o React e o Atlaskit nos plug-ins do Atlassian nos ambientes Servidor e Data Center.

Introdução


Atualmente, se você estiver desenvolvendo plug-ins para produtos Atlassian para servidor e data center, vm , soy , requirejs , jquery , backbone estarão disponíveis para desenvolver a interface do usuário a partir da caixa . Aqui aqui você pode ler o meu artigo sobre como usar disponível fora das bibliotecas caixa.

Essa pilha de tecnologia está desatualizada e gostaria de usar algo mais novo. Escolhi o texto datilografado e reajo como a pilha . Além disso, para facilitar a transferência de plug-ins de servidor para a nuvem, escolhi a biblioteca de atlaskit de elementos da interface do usuário .

Neste artigo, falarei sobre o Atlassian Jira, mas a mesma abordagem pode ser usada para outros produtos de servidor e data center da Atlassian.

Para reproduzir os exemplos deste artigo, você deve instalar o git e o Atlassian SDK .

Então, vamos começar!

Instale o arquétipo do Maven e crie um novo projeto.


Eu criei o arquétipo do Maven para facilitar a criação de um novo projeto que já conterá todas as configurações necessárias para criar um plugin com o React e o Atlaskit.

Se você não deseja usar o arquétipo, pode pegar o plug-in já criado nesse arquétipo e ir para a parte Nós montamos e iniciamos o projeto.

Clone o arquétipo do meu repositório Bitbucket:

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

Vá para a pasta jira-react-atlaskit-archetype e instale esse arquétipo no repositório local do Maven:

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

Depois disso, vá para a pasta um nível acima e crie um novo projeto com base neste arquétipo:

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

Será feita uma pergunta para selecionar o arquétipo necessário. Aqui estão meus arquétipos no repositório local:

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)

Preciso escolher ru.matveev.alexey.atlas.jira: jira-react-atlaskit-archetype-archetype, portanto, indiquei o número 3 como resposta.

Então você precisa especificar groupid e artifactid para o novo projeto:

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

Montamos e instalamos o projeto


No meu caso, o novo projeto está na pasta my-tutorial. Vamos para esta pasta e construa o projeto:

cd my-tutorial
atlas-mvn package

Após a montagem do projeto, vá para a pasta back-end e execute o Atlassian Jira:

cd backend
atlas-run

Plug-in de teste


Após o início do Atlassian Jira, vá para o navegador no seguinte endereço:

http://localhost:2990/jira

Você precisa fazer o login como administrador: admin e ir gear -> Gerenciar aplicativos.



Você verá um menu do nosso plugin. Porém, antes de iniciar nossos elementos Atlaskit, vá para Sistema -> Log e Criação de Perfil e defina o nível de log INFO para o pacote react.atlaskit.tutorial.servlet.



Agora volte para Gerenciar aplicativos e clique no menu Formulário. Veremos o formulário de entrada de dados que foi exibido usando o elemento Atlaskit Form :



Preencha todos os campos de texto e clique no botão Enviar:



Agora, se você abrir o arquivo atlassian-jira.log, verá algo assim:

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

Isso significa que, ao clicar no botão Enviar, nossos dados foram transferidos com sucesso para o servlet que serve este formulário e esse servlet exibiu os dados inseridos em um arquivo de log.
Agora vamos selecionar o menu Tabela dinâmica. Você verá o elemento Tabela dinâmica do Atlaskit :



Isso é tudo o que nosso plug-in faz. Agora vamos ver como tudo funciona!

Plug-in interno


Aqui está a estrutura do nosso plug-in: o



back - end contém o plug-in Atlassian Jira que foi criado usando o Atlassian SDK.
O frontend contém os elementos da interface do usuário que serão usados ​​pelo nosso plugin.
pom.xml arquivo pom no qual dois módulos são definidos:

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

Agora vamos olhar para a pasta frontal.

A parte dianteira


A pasta frontend contém os seguintes arquivos:



Descreverei os arquivos principais.

package.json é um arquivo de configuração para o npm que contém as seguintes informações:

  • lista de pacotes dos quais nosso projeto depende.
  • versões dos pacotes que usamos.

Usaremos pacotes como typescript, atlaskit, babel e outros.

.babel.rc - arquivo de configuração para Babel . Babel é usado para converter o código ECMAScript 2015+ em código JavaScript. Escreveremos nosso código Typcript, portanto, precisamos convertê-lo em código JavaScript para que o plugin Jira possa trabalhar com ele.

webpack.config.js - arquivo de configuração para o webpack . O Webpack processa nosso aplicativo, cria um gráfico de dependência e gera um pacote que contém todo o JavaScript necessário para que nosso aplicativo funcione. Para que o plug-in Jira funcione com nosso JavaScript, precisamos de um arquivo Javascript para cada ponto de entrada. No nosso caso, os pontos de entrada são os itens de menu Form e Dynamic Table.

Aqui está o conteúdo do arquivo 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")
    }
};

Como você pode ver, usamos atlassian-webresource-webpack-plugin .

É necessário que, depois que o webpack crie um arquivo JavaScript, esse arquivo seja automaticamente adicionado como um recurso da 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')
        }),
    ],

Como resultado dessa configuração, após criar o módulo frontend, o arquivo wr-defs.xml será criado na pasta backend / src / resources / META-INF / plugin-descriptors.

O parâmetro locationPrefix permite especificar a localização dos arquivos JavaScript no plug-in Jira. No nosso caso, indicamos que os arquivos estarão localizados na pasta backend / src / resources / frontend. Colocaremos os arquivos JavaScript nessa pasta posteriormente no módulo de back-end, mas agora esse parâmetro nos permite obter essa linha no arquivo wr-defs.xml <resource type = "download" name = "bundled.dynamictable.js" location = "frontend / bundled.dynamictable.js » />.

Aqui está o conteúdo do arquivo wr-defs.xml que foi criado durante o processo de construção do projeto:

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

Como você pode ver, temos seções adicionais de recursos da web nas quais os arquivos JavaScript criados pelo webpack são definidos. Tudo o que resta para nós é contar ao Jira, para que, ao instalar nosso plug-in, também utilizemos recursos da web na pasta backend / src / resources / META-INF / plugin-descriptor. Para fazer isso, fizemos as seguintes alterações no arquivo 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>

Adicionamos <Atlassian-Scan-Folders> META-INF / plugin-descriptors </Atlassian-Scan-Folders>. Este parâmetro informa ao Jira que é necessário procurar recursos adicionais da web na pasta META-INF / plugin-descriptors.

Também adicionamos <compressResources> false </compressResources> para desativar a minificação de nossos arquivos JavaScript. Eles já foram minificados.
Também definimos dois pontos de entrada para nosso aplicativo no arquivo webpack.config.js:

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

Isso significa que o webpack varrerá os arquivos ./src/form.js e ./src/dynamictable.js e criará dois arquivos JavaScript, cada um dos quais é um arquivo para um dos pontos de entrada. Esses arquivos serão criados na pasta frontend / dist.

./src/form.js e ./src/dynamictable.js não contêm nada de especial. Tirei a maior parte do código dos exemplos no Atlaskit.

Aqui está o conteúdo do arquivo form.js:

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

Aqui está apenas uma linha que importa a classe do arquivo ./js/components/Form.js.

Aqui está o conteúdo do arquivo ./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;
});

Aqui eu mostro o componente MyForm em um contêiner div. Esse contêiner será definido no modelo de plugin soy Jira.

Também preste atenção a esta linha:

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

document.getElementById ("contextPath"). value obtém o valor do campo com o id contextPath. Eu defino esse campo no modelo de soja no plugin Jira. O valor nesse campo vem do servlet ao qual o item de menu Form está vinculado. No meu caso, contextPath contém o valor / jira, pois ao iniciar o Jira a partir do Atlassian SDK, esse caminho de contexto é definido.

E é tudo sobre frontend. Como resultado da montagem do módulo frontend, obtemos dois arquivos JavaScript na pasta frontend / dist e xml com recursos adicionais da web na pasta backend / src / resources / META-INF / plugin-descriptors.

Agora vamos para o back-end.

Processo interno


Adicionei esses plugins ao arquivo 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 cria um arquivo maven.properties que contém todas as propriedades do Maven. Preciso da propriedade atlassian.plugin.key para chamar recursos da Web de servlets que estão vinculados aos itens de menu do nosso plugin.

O maven-resources-plugin pega os arquivos JavaScript da pasta frontend / dist e os copia para a pasta backend / resources / frontend.

Em seguida, criei itens de menu e fiz uma chamada para servlets a partir desses itens de menu.

Aqui estão as linhas do arquivo 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>

Portanto, temos menus e servlets chamados a partir desses itens de menu.
Agora vamos dar uma olhada nos 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();
    }}

Defino duas variáveis ​​resourceService e soyTemplateRenderer e inicializo essas variáveis ​​no construtor da classe. resourceService - um bean que lê propriedades do arquivo maven.properties. soyTemplateRenderer - Jira bean que pode chamar modelos de soja.
No método doGet, obtenho o valor da propriedade atlassian.plugin.key e o caminho do contexto. Depois passo o caminho do contexto como parâmetro para o modelo de soja e chamo o modelo de soja sob o nome servlet.ui.form.

Aqui está o conteúdo do arquivo de soja:

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

O código para os modelos é bastante simples. Chamo o recurso da web para o item de menu e crio uma div de contêiner que será usada pelo React.

Registrei o próprio arquivo soy em 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> 

É tudo o que você precisa fazer para usar o React e o Atlaskit nos plugins de servidor e data center Atlassian.

All Articles