Atlassian Jira. Путь костылье. Часть 2

Часть 1

В этой части мы начнем менять наше решение в лучшую сторону.

База данных

Как Вы помните мы создали таблицу user_managers в бд Postgres с помощью менеджера баз данных. Это плохое решение создавать таблицу напрямую в бд:

  • Вам нужно помнить, что была создана таблица, если Вы собираетесь переносить конфигурацию с одного экземпляра Jira на другой
  • Если Вы сделаете бэкап Jira коробочным способом (xml файл), то таблицы user_mappings в бэкапе не будет
  • Если придет новый администратор Jira, то он ничего не будет знать про эту таблицу. Вам нужно будет это написать где-то в документации и как-то сделать так, чтобы новый администратор эту документацию обязательно прочел. Лично я против документации к коду и к решениям. Код и решения должны быть понятны без документации.

Хорошо. А как создать таблицу лучше?

Можно найти файл WEB-INF/classes/entitydefs/entitymodel.xml. Этот файл содержит описание таблиц в БД Jira. Вот пример такого файла:

Вы можете добавить Вашу собственную таблицу через этот файл. К таблице Вы также можете добавить первичный ключ, индексы, но мы ограничимся простым решением:

<entity entity-name="user_managers" table-name="user_manager" package-name="">        <field name="user" col-name="user" type="long-varchar"/> 
<field name="manager" col-name="manager" type="long-varchar"/> 
</entity> 

И кроме этого еще добавим информацию о нашей таблице в файл WEB-INF/classes/entitydefs/entitygroup.xml

<entity-group group="default" entity="user_managers"/>

Теперь рестартуем Jira и после этого находим нашу таблицу в БД Jira.

Да, теперь в коробочном бэкапе данные о таблице будут, но:

  • Нам нужно перезапускать Jira, если мы добавляем таблицу, а это делает наш экземпляр Jira недоступным для пользователей.
  • Чтобы удалить таблицу нам нужно удалять соответствующие записи из entitymodel.xml и entitygroup.xml, удалить таблицы из БД Jira и перезапустить Jira. Опять наш экземпляр Jira будет недоступен для пользователей.
  • Если мы хотим перенести конфигурацию с одного экземпляра Jira на другой, то нам нужно не забыть переместить вот эти два файла: entitymodel.xml и entitygroup.xml.
  • Если мы обновим верисию Jira, то нам нужно будет вручную внести изменения в файлы entitymodel.xml и entitygroup.xml. Мы не можем просто скопировать старые файлы на место новых, так как в этом случае мы можем потерять изменения из новой версии Jira.
  • Нам нужно документировать наше решение, чтобы не забыть таблицу.

Как Вы видите недостатков очень много, а преимущество только одно. Позже поговорим о решении получше, чем создавать таблицу в БД.

JSP страницы

Никогда не используйте JSP страницы:

  • Вы можете очень легко получить дыру в безопасности
  • Jira Java API не проверяется и не подсказывается при написании JSP кода, а поэтому кодить будете долго
  • Вам нужно перегружать Jira каждый раз, как Вы поменяли что-то в коде. Это очень раздражает и затягивает процесс разработки
  • У Вас не будет ошибок в логах, если JSP код по каким-то причинам не отработал. Будь то синтаксические ошибки или логические.
  • Вам нужно задокументировать Ваше решение, чтобы ничего не потерять при переносе или обновлении.

Хорошо, если JSP плохо, то что тогда использовать?

Проект Jira как справочник

Давайте вспомним, зачем мы создавали таблицу в БД и JSP страницы. Нам нужно было где-то хранить информацию о менеджерах пользователей и управлять этой информацией. А что если просто создать проект в Jira, который назвать user_managers?

Напрмер, тикет в этом проекте будет выглядеть вот так:

Как Вы видите при создании тикета мы указываем пользователя и его менеджера. И после того, как тикет создан, мы можем рассматривать этот тикет как строку в таблице БД.

На экране нет поля summary. Оно нам не нужно, но оно обязательно для заполнения, поэтому я написал вот такой код, чтобы это поле скрыть и добавил этот код в баннер.

<script>
      setInterval(function(){ 
         $("input#summary").val("mapping");
         $("input#summary").parent().css("display", "None");
      }, 3000);
</script>

Вот. Теперь все работает

Я создал два тикета:

Итак, что мы имеем? У нас есть проект user_managers, который хранит информацию о пользователях и менеджерах. Мы можем добавлять, удалять и изменять эти данные. Поговорим позже, как мы будет эти данные доставать. Так что у нас все хорошо. Мы очень быстро получили таблицу и возможность ею управлять, используя возможности Jira. Очень хорошо!

У нас было требование, что только пользователь lookupmanager может управлять данными о менеджерах пользователей, и мы это можем легко реализовать с помощью схем разрешений Jira. Опять все очень хорошо получилось!

А в чем минус этого решения?

Минус в UI. UI позволяет проводить манипуляции с данными, но если мы хотим ввести какую-то дополнительную логику: например, ограничить список пользователей, или выбирать пользователей через какое-то диалоговое окно, или добавить визард для заполнения данных, то у нас будут проблемы. В этом случае нужно применять другие решения, о которых мы поговорим позже.

JavaScript: изменения здравого смысла

Итак, у нас есть неплохое решение по таблице с данными о менеджерах и пользователях. Теперь давайте посмотрим на наш Javascript.

Сразу видны следующие проблемы:

  • иногда наш браузер зависает
  • у нас задержки между скрытием кнопки Ready For Work или когда мы присваиваем значение manager1 в поле Approver.

Эти проблемы связаны с функцией setInterval.

Нужно что-то сделать с функцией setInterval. Первое, что приходит в голову, это уменьшить период ожидания между запуском функции и тогда задержек не будет. Но в этом случае зависания браузере станут более очевидны.

Хорошо. Давайте тогда сделаем функцию setInterval умнее. Нам же кнопку “Ready For Work” нужно спрятать только один раз. Поэтому как только мы ее спрятали, сразу будем прекращать работу функции setInterval. Код будет вот таким:

var hideActionInterval = setInterval(function(){ 
         if ($("#customfield_10301-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10301-val").text())) {
             $('#action_id_21').addClass('hidden');
             clearInterval(hideActionInterval);
           } else {
             clearInterval(hideActionInterval);
           }
         }
      }, 3000);

Мы добавили clearInterval(hideActionInterval) после того, как кнопка была спрятана.

Отлично! А теперь тоже самое для поля Approval.

А не получится! Потому что если мы прекратим работу setInterval, то мы не будем реагировать на смену поля Reporter. А нам нужно поменять поле Approver, если поле Reporter поменялось. Хм. А что делать-то?

Наченем того, что концепция с setInterval неправильна изначальна.

Наши функции должны работать только тогда, когда страница полностью загрузилась. Т.е. мы прячем кнопку сразу после того, как страница загрузилась и записываем менеджера в поле Approver сразу после загрузки страницы.

Ну, хорошо. А как понять, что страницы загрузилась? В jquery это будет как-то так:

 $(function() {
         if ($("#customfield_10301-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10301-val").text())) {
             $('#action_id_21').addClass('hidden');
           }
         }
      });

Мы использовали $(function() {}), чтобы подождать полную загрузку страницы. И это сработало для кнопки Ready For Work. А теперь сделаем тоже самое для поля Approver:

      $(function() {
         if ($("optgroup#reporter-group-suggested > option:selected").val()) {
            $("#customfield_10301").val("manager1");
            $("#customfield_10301").focus(function() {
                 this.blur();
            });
         }
      });

И это не работает. А почему? А потому, что наш JavaScript код в баннере при нажатии на кнопку Create не перечитывается. Он срабатывает до нажатия на кнопку Create. Так, а как пофиксить?

Давайте добавим JavaScript в описание поля Approver!!! Ведь описание поля всегда видно на экране создания тикета, а значит и наш JavaScript отработает:

И опять неудача. В новых версиях Jira так делать нельзя, а раньше это проходило. Но давайте подумаем, даже если бы это получилось, было бы это хорошим решением? Нет, потому что наш JavaScript расползся бы по всеq Jira и нам трудно было бы его собирать. Вот пришел бы новый админ, как бы он узнал, что мы в описании поля Approver запостили JavaScript? Нужно описания всех полей просмотреть. А ведь JavaScript можно не только в описание полей положить. Есть и другие надежные места в Jira. Тогда надо бы программку написать или в документации отобразить. В общем одно решение лучше другого.

Нет, так не пойдет. А что же делать? В google поискать? А как узнать, что мы там не найдем что-то наподобие setInterval?

В общем нужно в код Atlassian Jira лезть. Наконец-то мы до этого дошли!

До этого момента при поиске решений я смотрел Developer Console и использовал здравый смысл, но как Вы видите ничего хорошо так не придумаешь. Поэтому если у Вас решения создаются на основании Developer Console и здравого смысла, то Ваш код будет полон решений наподобие setInterval. Положиться на такие решения нельзя.

Ну, что? Заглянем в код. Может там что-то найдем. Исходный код Jira доступен Вам, если Вы купили лицензию Jira.

JavaScript: изучаем исходный код Jira

Если посмотреть на код, то Вы найдете событие NEW_CONTENT_ADDED. У этого события есть вот такие причины:

pageLoad: "pageLoad",
    inlineEditStarted: "inlineEditStarted",
    panelRefreshed: "panelRefreshed",
    criteriaPanelRefreshed: "criteriaPanelRefreshed",
    issueTableRefreshed: "issueTableRefreshed",

    //Fired when on List View, when we update the issue row with new information
    issueTableRowRefreshed: "issueTableRowRefreshed",

    //Fired when the Filters panel is opened
    filterPanelOpened: "filterPanelOpened",

    //Fired when the LayoutSwitcher has been rendered
    layoutSwitcherReady: "layoutSwitcherReady",

    //Fired when the user goes back to the search (JRADEV-18619)
    returnToSearch: "returnToSearch",

    //Fired when the Share dialog is opened
    shareDialogOpened: "shareDialogOpened",

    //Fired when the Search Filters results table has been refreshed
    filtersSearchRefreshed: "filtersSearchRefreshed",

    //Fired when the Search Dashboards results table has been refreshed
    dashboardsSearchRefreshed: "dashboardsSearchRefreshed",

    //Fired when a Tab is updated (eg: Project tabs, Manage Dashboard tabs...)
    tabUpdated: "tabUpdated",

    //Fired when a Dialog is ready to be displayed
    dialogReady: "dialogReady",

    //Fired when the Components table is ready
    componentsTableReady: "componentsTableReady",

    //Fired when a Workflow has been loaded on Project configuration
    workflowReady: "workflowReady",

    //Fired when a Workflow Header has been loaded on Project configuration
    workflowHeaderReady: "workflowHeaderReady",

    //Fired when content on the page was changed that does not fall into an alternative reason. This should be used
    //instead of creating new reasons. The NEW_CONTENT_ADDED API paradigm should be moved away from for new
    //development where possible.
    contentRefreshed: "contentRefreshed"

Когда Вы нажимаете на кнопку Create и загружается окно создания тикета, то срабатывает событие NEW_CONTENT_ADDED с причиной dialogReady и в этом момент нам нужно записать менеджера в поле Approver. Сделаем это:

$(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {
            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {
              $("#customfield_10301").val("manager1");
              $("#customfield_10301").focus(function() {
                 this.blur();
            });
            } 
        });
});

Все работает. Добавим туда же скрытие поля Summary:

      $(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {
            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {
              $("#customfield_10301").val("manager1");
              $("#customfield_10301").focus(function() {
                 this.blur();
              });
              $("input#summary").val("mapping");
              $("input#summary").parent().css("display", "None");
            } 
         });
      });

Все рабоает без зависаний и задержек:

А теперь, давайте сделаем вот что. Если значение поля Reporter admin, то менеджер в поле Approver будет manager1, иначе manager2.

Вот код:

$(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {
            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {
              if ($("optgroup#reporter-group-suggested > option:selected").val() == "admin") {
                $("#customfield_10301").val("manager1");
              } else {
                $("#customfield_10301").val("manager2");
              } 
              $("#customfield_10301").focus(function() {
                 this.blur();
              });
            } 
         });
      });

Если Вы откроете экран создания тикета под админом, то значение поля Approver будет manager1:

Если поменяете поле Reporter на user2, то значение не поменяется:

Почему? Проблема в том, что причина dialogReady работает один раз, когда окно открыто, оно не работает, если мы меняем значение поля Reporter, и поэтому наша функция по смене значения в поле Approver не работает. А что делать?

Можно опять setInterval сделать, но тогда вернутся задержки и зависания браузера. Нет, нужно опять исходный код Jira смотреть.

И на этот раз я не нашел ничего полезного в коде. Ну, тогда просто добавим onChange к полю Reporter с помощью $(“#reporter”).change( function(e) {):

$(function () {         
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {
            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {
               $("#reporter").change( function(e) {
                  if ($("optgroup#reporter-group-suggested > option:selected").val() == "admin") {
                    $("#customfield_10301").val("manager1");
                  } else {
                    $("#customfield_10301").val("manager2");
                  } 
               });
              if ($("optgroup#reporter-group-suggested > option:selected").val() == "admin") {
                $("#customfield_10301").val("manager1");
              } else {
                $("#customfield_10301").val("manager2");
              } 
              $("#customfield_10301").focus(function() {
                this.blur();
              }); 
            } 
         });
     });

Так, теперь все работает. Если мы меняем значение поля Reporter, то меняется и значение поля Approver.

Хорошее ли это решение добавить событие onChange к полю Reporter? Неизвестно. Я не посмотрел весь код Jira. Возможно, это где-то нам вернется.

И такие вопросы возникают постоянно при написании кода JavaScript. А хорошо ли наше решение на JavaScript? Это неизвестно наперед. Вы никогда не знаете где и какой баг вылезет. А если он вылезет, то какая строка Вашего кода этот баг вызывала, потому JavaScript код в баннере сложно дебажить.

JavaScript: рефакторинг

Итак, вот наш весь код JavaScript:

<script>
      $(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {

            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {
               $("#reporter").change( function(e) {
                  if ($("optgroup#reporter-group-suggested > option:selected").val() == "admin") {
                    $("#customfield_10301").val("manager1");
                  } else {
                    $("#customfield_10301").val("manager2");
                  } 
               });

              if ($("optgroup#reporter-group-suggested > option:selected").val() == "admin") {
                $("#customfield_10301").val("manager1");
              } else {
                $("#customfield_10301").val("manager2");
              } 
              $("#customfield_10301").focus(function() {
                this.blur();
              });
              $("input#summary").val("mapping");
              $("input#summary").parent().css("display", "None");
            } 
         });
         if ($("#customfield_10301-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10301-val").text())) {
             $('#action_id_21').addClass('hidden');
           }
         }
     });
</script>

Красиво? По-моему не очень. Сложно понять, что в коде происходит. Давайте зарефакторим:

<script>

      function setApproverFieldByReporter() {
         if ($("optgroup#reporter-group-suggested > option:selected").val() == "admin") {
              $("#customfield_10301").val("manager1");
         } else {
              $("#customfield_10301").val("manager2");
         } 
      };

      function disableAporoverField() {
          $("#customfield_10301").focus(function() {
                this.blur();
          });
      };

      function hideSummaryField() {
          $("input#summary").val("mapping");
          $("input#summary").parent().css("display", "None");
      }

      function hideReadyForWorkButtonConditionally() {
         if ($("#customfield_10301-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10301-val").text())) {
             $('#action_id_21').addClass('hidden');
           }
         }
      }

      $(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {

            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {

              $("#reporter").change( function(e) {
                 setApproverFieldByReporter();
              });
              setApproverFieldByReporter();
              disableAporoverField();
              hideSummaryField();
            } 
         });
         hideReadyForWorkButtonConditionally();
     });
</script>

Я большинство логики вынес в функции и теперь, по крайней мере, верхнеуровнево понятно, что происходит:

 $(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {

            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {

              $("#reporter").change( function(e) {
                 setApproverFieldByReporter();
              });
              setApproverFieldByReporter();
              disableAporoverField();
              hideSummaryField();
            } 
         });
         hideReadyForWorkButtonConditionally();

Но все еще есть плохой код с добавлением событий NEW_CONTENT_ADDED и Onchange. Но этот рефакторинг я оставлю Вам)

В нашем коде мы реализовали только 4 требования, а кода уже много и он непонятен. Представьте, что будет, если вы реализуете 100 требований. Думаю можно будет увольняться или переходит в разработку микросервисов)

JavaScript: получаем данные из проекта user_managers

Давайте будет читать информацию о менеджерах пользователей из проекта user_managers. Для того будем использовать /rest/api/2/search метод:

 function setApproverFieldByReporter() {
        $.get( "http://localhost:8080/rest/api/2/search?jql=project%3DUS%20and%20%22user%22%20%3D%20" + $("#reporter").val(), function( data ) {
             $("#customfield_10301").val( data.issues[0].fields.customfield_10201.name);
       
         });        
        
         
      };
$(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {
            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {
              $("#reporter").change( function(e) {
                  setApproverFieldByReporter();
              });
              setApproverFieldByReporter();
            } 
         });
});

setApproverFieldByReporter выбирает тикеты из проекта user_managers, где поле user равно значению поля Reporter ( $(“#reporter”).val()) и устанавливает в поле Approver значение из поля customfield_10201.

Весь скрипт выглядит вот так:

<script>
     function setApproverFieldByReporter() {
        $.get( "http://localhost:8080/rest/api/2/search?jql=project%3DUS%20and%20%22user%22%20%3D%20" + $("#reporter").val(), function( data ) {
             $("#customfield_10301").val( data.issues[0].fields.customfield_10201.name);       
         });                         
      };

      function disableAporoverField() {
          $("#customfield_10301").focus(function() {
                this.blur();
          });
      };

      function hideSummaryField() {
          $("input#summary").val("mapping");
          $("input#summary").parent().css("display", "None");
      }

      function hideReadyForWorkButtonConditionally() {
         if ($("#customfield_10301-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10301-val").text())) {
             $('#action_id_21').addClass('hidden');
           }
         }
      }

      $(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {

            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {

              $("#reporter").change( function(e) {
                
                  setApproverFieldByReporter();
              });
              setApproverFieldByReporter();
              disableAporoverField();
              hideSummaryField();
            } 
         });
         hideReadyForWorkButtonConditionally();
     });
</script>

Видно, что скрипт совсем не обрабатывает ошибочные ситуации. Например, что если не найдено ни одного менеджера для пользователя или найдено более одного менеджера? Таким образом, этот скрипт должен еще обрасти жирком и стать более непонятным.

Тестируем наш JavaScript код

Если Вы начнете тестировать наш код, то увидите, что поле Summary скрывается для всех проектов, а нам нужно только для проекта US. Нам нужно добавить соответствующую логику. Я не буду этого делать. Вы можете это сделать сами.

Мы ищем менеджера для поля Reporter во всех проектах, а нам нужно только для AP. Эту логику тоже нужно добавить.

Как Вы помните кнопка “Ready For Work” прячется, если текущий пользователь не равен пользователю в поле Approver. Но мы спрятали эту кнопку только на экране просмотра тикета. А ведь переходы можно делать и во многих других местах в Jira. Например, Issue Navigator:

Как Вы видите, кнопка “Ready For Work” доступна. Но это не беда. Просто нужно найти все места, где можно сделать переход и спрятать кнопку))).

Другая проблема, если я нажму на AP-2 в Issue Navigator, то откроется экран просмотра тикета и на этом экране кнопка”Ready For Work” будет доступна. Ого! Мы же ее скрыли! А проблема в том, что JavaScript в баннере не перечитывается и поэтому кнопка не скрывается. Как будем решать? setInterval? Посмотрим исходный код?

Как Вы видите проблем возникает много даже с простыми требованиями.

Читабельность JavaScript кода

И снова наш код:

<script>
     function setApproverFieldByReporter() {
        $.get( "http://localhost:8080/rest/api/2/search?jql=project%3DUS%20and%20%22user%22%20%3D%20" + $("#reporter").val(), function( data ) {
             $("#customfield_10301").val( data.issues[0].fields.customfield_10201.name);       
         });                         
      };

      function disableAporoverField() {
          $("#customfield_10301").focus(function() {
                this.blur();
          });
      };

      function hideSummaryField() {
          $("input#summary").val("mapping");
          $("input#summary").parent().css("display", "None");
      }

      function hideReadyForWorkButtonConditionally() {
         if ($("#customfield_10301-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10301-val").text())) {
             $('#action_id_21').addClass('hidden');
           }
         }
      }

      $(function () {
         JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function (e, $context, reason) {

            if (reason == JIRA.CONTENT_ADDED_REASON.dialogReady) {

              $("#reporter").change( function(e) {
                
                  setApproverFieldByReporter();
              });
              setApproverFieldByReporter();
              disableAporoverField();
              hideSummaryField();
            } 
         });
         hideReadyForWorkButtonConditionally();
     });
</script>

Это ужас. У нас много магических чисел, строк, ид: customfield_10201, action_id_21, customfield_10301, http://localhost:8080/ и это делает наш код непонятным.

И я могу сказать, что весь код, который я видел в баннерах, выглядит таким образом. А как сделать его лучше? Нам же ничего не доступно. Ни TypeScript, ни современные версии ECMAscript. Ваш код в любом случае будет ужасен.

Делаем код JavaScript переносимым

Другая проблема заключается в том, что Вы не сможете легко перенести Ваш код на другие инстансы Jira. В коде у нас используются ид полей, переходов: customfield_10201, action_id_21, customfield_10301. А эти ид будут отличаться на других экземплярах Jira. Вам нужно как-то получать ид по именам полей, переходов. Думаете так можно? Думаю, нет. Ведь, например, имена полей могут дублироваться. В этом случае Вы не можете однозначно получить ид поля. Об этой проблеме я подробно поговорю позже.

Вы можете сказать, что в нашей Jira не могут быть дублирования полей. Тогда Вы можете использовать Jira REST API для получения ид полей по имени поля. Но у Вас код вырастит в объемах, что очень плохо для JavaScript в баннере.

Вывод по JavaScript коду в баннере

Если Вы используете JavaScript в баннере, то у Вас будут вот такие проблемы:

  • Код нечитаем: Вы не можете использовать современные версии ECMAscript или Typescript, Вы не можете разделить скрипт на файлы(можете попробовать и ничего хорошего не получится), большое количество магии.
  • Баг на баге!!! Разработчики Atlassian Jira, когда разрабатывали Jira, не думали, что пользователи будут дорабатывать UI Jira через баннер, поэтому Вы будете встречать баги постоянно. Ваш код просто не будет вписываться в код разработчиков Jira
  • Код трудно переносить между экземплярами Jira
  • Ваш код нельзя протестировать юнит тестами. Даже если Вы попытаетесь, то код станет непонятным и Вы будете тащить зависимости для тестирования даже когда они будут ненужны. А это будет грузить Jira.
  • Баннер работает везде, поэтому Ваш скрипт для работы на форме создания тикета будет подтягиваться тоже везде. А это будет грузить Jira.
  • Вы не сможете быстро отключить какую-то часть логики, так как у Вас будет spaghetti код

Как итог, я бы сказал, что никогда не пишите JavaScript в баннере. Для этого есть другие решения. Поговорим об этом позже.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

2 thoughts on “Atlassian Jira. Путь костылье. Часть 2

Leave a Reply

%d bloggers like this:

Spelling error report

The following text will be sent to our editors: