رد فعل و Atlaskit في الخادم الإضافي ومركز البيانات في Atlassian

تحية للجميع!

في هذه المقالة ، أود أن أتحدث عن كيفية استخدام React و Atlaskit في المكونات الإضافية Atlassian في بيئات الخادم ومركز البيانات.

المقدمة


في الوقت الحالي ، إذا كنت تطور مكونات إضافية لمنتجات Atlassian للخادم ومركز البيانات ، فإن vm و soy و requiredjs و jquery و backbone متاحة لك لتطوير واجهة المستخدم من الصندوق . هنا هنا يمكنك أن تقرأ مقالتي حول كيفية استخدام خارج متاح للمكتبات مربع.

حزمة التكنولوجيا هذه قديمة وترغب في استخدام شيء أحدث. اخترت نسخة مطبوعة على الآلة الكاتبة و تتفاعل كما مكدس . أيضًا ، من أجل تسهيل نقل المكونات الإضافية للخادم إلى السحابة ، اخترت مكتبة عناصر واجهة المستخدم atlaskit .

في هذه المقالة سوف أتحدث عن Atlassian Jira ، ولكن يمكن استخدام نفس النهج لخادم Atlassian ومنتجات مركز البيانات الأخرى.

من أجل إعادة إنتاج الأمثلة من هذه المقالة ، يجب تثبيت git و Atlassian SDK .

لذا ، لنبدأ!

قم بتثبيت النموذج الأصلي لـ Maven وأنشئ مشروعًا جديدًا.


لقد صنعت النموذج الأصلي لـ Maven لتسهيل إنشاء مشروع جديد سيحتوي بالفعل على جميع الإعدادات اللازمة لإنشاء مكون إضافي باستخدام React و Atlaskit.

إذا كنت لا ترغب في استخدام النموذج الأصلي ، فيمكنك أخذ المكون الإضافي الذي تم إنشاؤه بالفعل من هذا النموذج الأصلي والانتقال إلى الجزء الذي نجمعه ونبدأ المشروع.

استنساخ النموذج الأصلي من مستودع Bitbucket الخاص بي:

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

انتقل إلى مجلد مجلد jira-response-atlaskit-archetype وقم بتثبيت هذا النموذج الأصلي في مستودع Maven المحلي:

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

بعد ذلك ، انتقل إلى المجلد بمستوى واحد أعلى وقم بإنشاء مشروع جديد بناءً على هذا النموذج الأصلي:

cd ..
atlas-mvn archetype:generate -DarchetypeCatalog=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)

من الضروري اختيار ru.matveev.alexey.atlas.jira: jira-React-atlaskit-archetype-archetype ، لذلك أشرت إلى الرقم 3 كإجابة.

ثم تحتاج إلى تحديد groupid و artifactid للمشروع الجديد:

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

نقوم بتجميع وتثبيت المشروع


في حالتي ، المشروع الجديد في مجلد البرنامج التعليمي الخاص بي. دعنا نذهب إلى هذا المجلد وإنشاء المشروع:

cd my-tutorial
atlas-mvn package

بعد تجميع المشروع ، انتقل إلى مجلد الواجهة الخلفية وقم بتشغيل Atlassian Jira:

cd backend
atlas-run

اختبار البرنامج المساعد


بعد بدء Atlassian Jira ، انتقل إلى المتصفح على العنوان التالي:

http://localhost:2990/jira

تحتاج إلى تسجيل الدخول كمسؤول: admin و go gear -> إدارة التطبيقات.



سترى قائمة من البرنامج المساعد لدينا. ولكن قبل بدء عناصر Atlaskit الخاصة بنا ، انتقل إلى System -> Logging and Profiling وقم بتعيين مستوى تسجيل INFO لحزمة response.atlaskit.tutorial.servlet.



عد الآن إلى إدارة التطبيقات وانقر على قائمة النموذج. سنرى نموذج إدخال البيانات الذي تم عرضه باستخدام عنصر نموذج Atlaskit :



املأ جميع الحقول النصية وانقر على الزر إرسال:



الآن ، إذا قمت بفتح ملف atlassian-jira.log ، سترى شيئًا مثل هذا:

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

هذا يعني أنه من خلال النقر فوق الزر "إرسال" ، تم نقل بياناتنا بنجاح إلى servlet الذي يخدم هذا النموذج ، وعرض هذا servlet البيانات التي تم إدخالها في ملف سجل.
الآن دعنا نختار قائمة Dynamic Table. سترى عنصر الجدول الديناميكي Atlaskit :



هذا كل ما يفعله المكون الإضافي. الآن دعونا نرى كيف يعمل كل شيء!

البرنامج المساعد داخل


إليك بنية المكون الإضافي الخاص بنا:



تحتوي الواجهة الخلفية على المكوّن الإضافي Atlassian Jira الذي تم إنشاؤه باستخدام Atlassian SDK.
تحتوي الواجهة الأمامية على عناصر واجهة المستخدم التي سيتم استخدامها بواسطة المكون الإضافي الخاص بنا.
ملف pom.xml pom حيث يتم تعريف وحدتين:

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

الآن دعونا نلقي نظرة على المجلد الأمامي.

نهاية المقدمة


يحتوي مجلد الواجهة الأمامية على الملفات التالية: سوف



أصف الملفات الرئيسية.

package.json هو ملف تكوين لـ npm يحتوي على المعلومات التالية:

  • قائمة الحزم التي يعتمد عليها مشروعنا.
  • إصدارات الحزم التي نستخدمها.

سوف نستخدم حزم مثل Typcript، atlaskit، babel وغيرها.

.babel.rc - ملف تهيئة بابل . يستخدم Babel لترجمة كود ECMAScript 2015+ إلى كود JavaScript. سنقوم بكتابة كود Typescript الخاص بنا ، لذلك نحتاج إلى ترجمته إلى كود JavaScript حتى يعمل المكون الإضافي Jira معه.

webpack.config.js - ملف التكوين لحزمة الويب . يقوم Webpack بمعالجة تطبيقنا ، وإنشاء رسم بياني للاعتماد وإنشاء حزمة تحتوي على كل جافا سكريبت الضروري لتطبيقنا للعمل. لكي يعمل المكوِّن الإضافي Jira مع جافا سكريبت لدينا ، نحتاج إلى ملف Javascript واحد لكل نقطة إدخال. في حالتنا ، نقاط الدخول هي نموذج عناصر القائمة والجدول الديناميكي.

فيما يلي محتويات ملف 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")
    }
};

كما ترى ، نحن نستخدم atlassian-webresource-webpack-plugin .

هناك حاجة لذلك بعد أن تقوم حزمة الويب بإنشاء ملف JavaScript ، ستتم إضافة هذا الملف تلقائيًا كمورد ويب:

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

نتيجة لهذا التكوين ، بعد إنشاء وحدة الواجهة الأمامية ، سيتم إنشاء ملف wr-defs.xml في مجلد الواجهة الخلفية / src / resources / META-INF / plugin-descriptors.

تسمح لنا المعلمة locationPrefix بتحديد موقع ملفات JavaScript في مكون Jira الإضافي. في حالتنا ، نشير إلى أن الملفات ستكون موجودة في مجلد الواجهة الخلفية / src / resources / frontend. سنضع ملفات JavaScript في هذا المجلد لاحقًا في وحدة الواجهة الخلفية ، ولكن الآن تسمح لنا هذه المعلمة بالحصول على مثل هذا السطر في الملف wr-defs.xml <resource type = "download" name = "المجمعة ..damamictable.js" location = "frontend / package.dynamictable.js » />.

فيما يلي محتويات ملف wr-defs.xml الذي تم إنشاؤه أثناء عملية إنشاء المشروع:

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

كما ترى ، لدينا أقسام إضافية من موارد الويب يتم فيها تحديد ملفات JavaScript التي تم إنشاؤها بواسطة webpack. كل ما تبقى لنا هو إخبار Jira أنه عند تثبيت المكون الإضافي ، فإننا نستخدم أيضًا موارد الويب من مجلد الواجهة الخلفية / src / resources / META-INF / plugin-descriptor. للقيام بذلك ، قمنا بإجراء التغييرات التالية على ملف 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>

لقد أضفنا <Atlassian-Scan-Folders> META-INF / plugin-descriptors </Atlassian-Scan-Folders>. ستخبر هذه المعلمة Jira أنه من الضروري البحث عن موارد ويب إضافية في مجلد META-INF / plugin-descriptors.

أضفنا أيضًا <compressResources> خطأ </ compressResources> لتعطيل تصغير ملفات جافا سكريبت. لقد تم تصغيرها بالفعل.
قمنا أيضًا بتحديد نقطتي إدخال لتطبيقنا في ملف webpack.config.js:

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

هذا يعني أن حزمة الويب ستقوم بفحص ملفات ./src/form.js و ./src/dynamictable.js وإنشاء ملفين لجافا سكريبت ، كل منهما ملف لإحدى نقاط الإدخال. سيتم إنشاء هذه الملفات في المجلد الأمامي / المجلد.

لا تحتوي ./src/form.js و ./src/dynamictable.js على أي شيء خاص. أخذت معظم التعليمات البرمجية من الأمثلة في Atlaskit.

هنا محتويات ملف form.js:

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

إليك سطر واحد فقط يستورد الفئة من الملف ./js/components/Form.js.

فيما يلي محتويات ملف ./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;
});

أعرض هنا مكون MyForm في حاوية div. سيتم تعريف هذه الحاوية في قالب المكوِّن الإضافي لفول الصويا.

انتبه أيضًا لهذا الخط:

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

document.getElementById ("contextPath"). تحصل القيمة على قيمة الحقل باستخدام id contextPath. أحدد هذا الحقل في قالب الصويا في البرنامج المساعد Jira. تأتي القيمة في هذا الحقل من servlet التي يرتبط بها عنصر قائمة Form. في حالتي ، يحتوي contextPath على القيمة / jira ، لأنه عند بدء Jira من Atlassian SDK ، يتم تعيين مسار السياق هذا.

والأمر كله يتعلق بالواجهة الأمامية. نتيجة لتجميع وحدة الواجهة الأمامية ، نحصل على ملفي JavaScript في المجلد الأمامي / dist و xml مع موارد ويب إضافية في الواجهة الخلفية / src / resources / META-INF / plugin-descriptors.

الآن دعنا ننتقل إلى الخلفية.

الخلفية


أضفت هذه الإضافات إلى ملف 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 ينشئ ملف maven.properties يحتوي على جميع خصائص Maven. أحتاج إلى خاصية atlassian.plugin.key لاستدعاء موارد الويب من servlets المرتبطة بعناصر القائمة في المكون الإضافي الخاص بنا.

يلتقط البرنامج المساعد maven-resources-plugin ملفات جافا سكريبت من مجلد الواجهة الأمامية / المجلد وينسخها إلى مجلد الواجهة الخلفية / الموارد / الواجهة الأمامية.

ثم قمت بإنشاء عناصر القائمة وقمت بإجراء مكالمة إلى servlets من عناصر القائمة هذه.

إليك الخطوط من ملف 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>

لذا ، لدينا قوائم وخوادم صغيرة يتم استدعاؤها من عناصر القائمة هذه.
الآن دعونا نلقي نظرة على 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();
    }}

أحدد متغيري ResourceService و soyTemplateRenderer وأهيئ هذه المتغيرات في مُنشئ الفئة. ResourceService - حبة تقرأ الخصائص من ملف maven.properties. soyTemplateRenderer - فول جيرا الذي يمكنه استدعاء قوالب فول الصويا.
في طريقة doGet ، أحصل على قيمة الخاصية atlassian.plugin.key ومسار السياق. ثم أمرر مسار السياق كمعلمة لقالب الصويا وأستدعي قالب الصويا تحت اسم servlet.ui.form.

فيما يلي محتويات ملف الصويا:

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

كود القوالب بسيط للغاية. أتصل بمصدر الويب لعنصر القائمة وأنشئ div div سيتم استخدامه بواسطة React.

لقد سجلت ملف الصويا نفسه في 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> 

هذا كل ما عليك فعله لاستخدام React و Atlaskit في خادم Atlassian وملحقات مركز البيانات.

All Articles