728x90
반응형
SMALL

JIRA 플러그인을 개발할 땐 플러그인이 허용하는 범위 내에서 새로운 섹션을 추가할 수 있다.

예를 들어, JIRA 관리자라면 볼 수 있는 화면인 이 화면을 보자.

이 화면에 Manage apps 탭을 선택하면 보이는 좌측 사이드바에 플러그인의 메뉴들을 등록할 수 있다.

사진에 보이는것처럼 K-APPROVAL도 만들고 있는 플러그인의 메뉴이다.

 

Web Section과 Web Item

이걸 어떻게 하는지 하나씩 알아보자. 방법은 크게 2가지가 있다.

  • 배치 스크립트 실행
  • 직접 등록

우선 배치 스크립트를 실행하는건 다음 명령어를 실행하는 것이다.

atlas-create-jira-plugin-module

이 명령어를 프로젝트의 루트 경로, 다른 말로 프로젝트의 pom.xml 파일이 존재하는 경로에서 실행하면 이런 화면이 보일것이다.

이게 커스텀하고 새로 만들어낼 수 있는 여러 모듈들이다. 그 중 Web Section이 보일것이다. 30번. 

이걸 선택해서 진행하면 되는데, 나는 직접 등록하는 방식으로 Admin 화면에 메뉴를 새로 만드는 걸 작성하고자 한다. 이젠 직접 등록하는 방식이 더 편하기도 하고. 

 

그러기 위해선 일단 atlassian-plugin.xml 파일에 아래와 같이 작성을 하나 해준다.

<web-section key="adminPluginsMenuSection" name="k-approval admin plugins menu section" location="admin_plugins_menu" >
    <label key="menu.admin.section"/>
</web-section>

web-section 태그에서 가장 중요한 attribute는 location이다. 이 location은 아무값이나 적을 수 있는게 아니고 지정된 값만을 적을 수 있다. 그리고 그 지정된 값들 중에 관리자 화면에 있는 Manage Apps에 메뉴로 등록할 수 있는 위치는 `admin_plugins_menu`이다.

 

이걸 어떻게 알았냐면 공식 문서를 진짜 열심히 찾다보면 겨우 찾을 수 있다. 찾기가 굉장히 어렵다.

 

Administration UI locations

The section attribute of this web item module (for versions of Jira prior to 4.4) defines which section of the Administration area the web item will appear in.

developer.atlassian.com

위 링크에 들어가면 관리자 화면의 locations 정보를 알 수 있다. 

 

다시 돌아와서, web-section 태그에서 key, name, location attritbutes를 작성했는데 key, name은 원하는 값을 적으면 된다. 그렇지만 Unique해야 한다. 이 key를 통해서 섹션 아래 여러 아이템(메뉴버튼)들을 등록할 수 있기 때문에.

 

그리고 label은 보여지는 값을 의미한다. 그러니까 아래 사진에 빨간 박스.

그리고 여기선, 국제화를 위해 i18n 리소스를 사용했다. menu.admin.section 이라는 키는 i18n 리소스 파일에서 등록한 값이다.

kapproval.properties

menu.admin.section=K-Approval
...

kapproval_ko.properties

menu.admin.section=k-결재

 

여기까지 하면 web-section에 대한 내용은 다 한 것이다. 이제 그 섹션아래 여러 버튼이 존재한다. 아래 사진을 봐보자.

섹션 아래 여러 버튼들이 있다. 이것들은 web-item이라는 리소스이다. 이것들도 다 atlassian-plugin.xml에 등록해줘야 한다.

그래서 다음과 같이 작성해준다.

atlassian-plugin.xml

<web-item key="approvalManagement" name="k-approval management" section="admin_plugins_menu/adminPluginsMenuSection" >
    <label key="menu.admin.section.approval"/>
    <link>/plugins/servlet/kapproval/admin/approval</link>
</web-item>

여기서 key, name attributes는 역시 원하는 값을 그대로 넣어주면 된다. 중요한 것은 section attribute.

보면 `admin_plugins_menu/adminPluginsMenuSection`이라고 되어 있다. 

admin_plugins_menu 이 부분은 정해져 있는 값이다. 위에서도 다룬 바 있다. 그리고 adminPluginsMenuSection 이 값은 위에 만든 web-section 태그의 key 값이다. 그래서 합쳐지면 관리자 화면의 내가 만든 섹션 하단에 아이템을 넣겠다는 의미가 된다.

 

그리고 여기서도 label은 역시 i18n 리소스를 사용한것이다.

link 태그는 이제 이 버튼(아이템)을 클릭하면 보여질 화면에 대한 path라고 생각하면 된다. 그리고 이건 서블릿을 등록하고 서블릿을 코드를 직접 만들어야 한다. 그래서 일단은 link 태그를 위처럼 작성해두자. 아 물론 만약 버튼을 클릭한게 구글 브라우저를 띄우는 거라면 그냥 아래처럼 하면 된다.

<link>https://www.google.com</link>

아무튼 결국 이 link 태그는 버튼을 클릭하면 이동하는 링크이다.

 

그럼 아래가 위에서 다룬 전체 코드이다.

atlassian-plugin.xml

<web-section key="adminPluginsMenuSection" name="k-approval admin plugins menu section" location="admin_plugins_menu" >
    <label key="menu.admin.section"/>
</web-section>

<web-item key="approvalManagement" name="k-approval management" section="admin_plugins_menu/adminPluginsMenuSection" >
    <label key="menu.admin.section.approval"/>
    <link>/plugins/servlet/kapproval/admin/approval</link>
</web-item>

여기까지 한다고 끝난건 아니다. 이제 서블릿을 등록하고 서블릿 코드를 작성해야 한다. 

 

Web Item에 대한 Servlet

https://developer.atlassian.com/server/framework/atlassian-sdk/creating-an-admin-configuration-form/#render-the-form-with-the-atlassian-template-renderer

 

Creating an admin configuration form

Namespace your keys! Because this is global application configuration data, it is important that you do some kind of namespacing with your keys. Whether you use your plugin key, a class name or something else entirely is up to you. Just make sure it is uni

developer.atlassian.com

 

서블릿을 등록하려면 먼저 서블릿 클래스를 만들어야 한다.

나도 2024년에 서블릿을 직접 사용해서 MVC 구조를 만들줄은 몰랐는데, Atlassian JIRA DC 플러그인을 만들려면 이게 정해진 규칙이니 따라야 했다. 우선 의존성을 내려받아야 한다. 

 

pom.xml

<dependency>
    <groupId>com.atlassian.templaterenderer</groupId>
    <artifactId>atlassian-template-renderer-api</artifactId>
    <version>1.1.1</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.3</version>
    <scope>provided</scope>
</dependency>

두 가지가 필요한데 나는 뷰 템플릿을 사용한다. JIRA Plugin 개발에서 공식문서에 나와있는 뷰 템플릿은 Velocity인데, 다른게 가능한지는 모르겠다. 크게 다른게 없기 때문에 그냥 Velocity를 사용해도 무방하다고 생각한다. 그리고 이 템플릿을 렌더링 하는 Atlassian의 라이브러리인 ATR(Atlassian Template Renderer)를 사용한다.

 

그리고 Servlet을 사용하려면 관련 의존성을 내려 받아야 하기 때문에 두가지 dependencies를 추가한다.

 

이제 Servlet 클래스를 하나 만들어보자.

ApprovalManagementServlet

package kr.osci.kapproval.admin.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ApprovalManagementServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {}
}

우선 HttpServlet을 상속받아야 한다. 서블릿을 사용할거라면!

 

첫번째로 할 일은 doGet()을 오버라이딩 하는 것이다. 뭐 GET인 이유는 데이터를 전송하는 과정이 아니라 그저 관리자 화면의 특정 메뉴를 클릭하면 보여지는 화면을 구현할것이기 때문에.

 

그리고, 이게 관리자 화면이다 보니까 지금 요청한 사람이 실제로 관리자인지 아닌지 검증이 필요하다.

그리고 Atlassian에서 제공하는 방법 중 편리하게 사용할 수 있는 WebSudoManager라는게 있다.

 

Adding WebSudo support to your plugin

Adding WebSudo support to your plugin Support for Secure Administrator Sessions (also called websudo) was added in Confluence 3.3 and Jira 4.3. When an administrator who is logged into Confluence or Jira attempts to access an administration function, they

developer.atlassian.com

WebSudoManager가 뭘 해주는지 위 링크에서 자세하게 볼 수 있고 간단하게 설명해서 현재 요청한 사람이 관리자인지 아닌지 체크해서 화면을 보여주거나 로그인 화면으로 이동시키거나 둘 중 하나를 해주는 서비스라고 생각하면 된다.

 

그래서 다음과 같이 WebSudoManager를 필드로 선언해준다.

@ComponentImport private final WebSudoManager webSudo;

참고로 이 친구의 패키지는 다음과 같다.

com.atlassian.sal.api.websudo.WebSudoManager;

그리고 이 라이브러리는 기본으로 이 라이브러리가 내포하고 있다. 그리고 이 라이브러리 역시 기본으로 설정되어 있다.

<dependency>
    <groupId>com.atlassian.jira</groupId>
    <artifactId>jira-api</artifactId>
    <version>${jira.version}</version>
    <scope>provided</scope>
</dependency>

 

그리고 뷰 템플릿을 렌더링 할 것이기 때문에 TemplateRenderer 클래스 역시 주입받아야 한다.

@ComponentImport private final TemplateRenderer renderer;

 

그리고 이전 포스팅에서 스프링 스캐너를 사용해서 애노테이션 기반의 주입을 사용하게 설정했었다. 그래서 @ComponentImport 애노테이션을 사용해서 필요한 모듈을 이렇게 쉽게 주입받을 수가 있다. 이 @ComponentImport는 주입받는 모든것들에 다 적용하는건 아니고 Atlassian 패키지 하위에 있는 모듈들을 주입받을때만 사용하면 된다. 이 말은 이후에 좀 더 잘 이해하게 된다.

 

그래서 가장 먼저 실행할 코드는 다음과 같다.

ApprovalManagementServlet

package kr.osci.kapproval.admin.servlet;

import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.sal.api.websudo.WebSudoManager;
import com.atlassian.templaterenderer.TemplateRenderer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@RequiredArgsConstructor
public class ApprovalManagementServlet extends HttpServlet {

    @ComponentImport private final TemplateRenderer renderer;

    @ComponentImport private final WebSudoManager webSudo;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        webSudo.willExecuteWebSudoRequest(req);

    }
}

스프링에 익숙한 사람들은 생성자 주입을 통해 주입받는다는 것을 알 것이다. 그리고 Lombok을 사용하면 @RequiredArgsConstructor 애노테이션으로 편리하게 생성자를 만들 수 있다. 그래서 위 코드가 그 방식을 따르고 있다. 

 

그리고 doGet() 안에서 WebSudoManagerwillExecuteWebSudoRequest(req); 를 호출해주면 현재 요청을 관리자 권한을 가진 유저가 요청한것인지 확인해준다. 그 이후에 코드가 실제 이 서블릿을 호출할 때 실행될 코드가 된다. 특별히 뭐 다른게 필요없다면 바로 뷰 템플릿을 렌더링하면 된다.

renderer.render("templates/admin/approvals.vm", resp.getWriter());

이 코드가 이제 뷰 템플릿을 렌더링하는 코드이다. 뷰 템플릿의 경로는 classpath(`src/main/resources`) 아래 templates에 넣어두면 된다. 이건 스프링 부트랑 다른게 없기 때문에 쉽게 와 닿았다.

 

근데 뷰 템플릿을 사용하는 이유는 동적으로 뷰를 렌더링하기 위함이다. 그렇다는 것은 데이터가 동적으로 변경되고 필요한 데이터가 그때 그때 달라질텐데 그럴때 데이터를 담아 전송하는 방법은 간단하게 Map으로 Key/Value 자료구조를 넘기면 된다.

Map<String, Object> context = new HashMap<>();
context.put("approvalPaths", "pathA");

 

이렇게 만든 context를 다음과 같이 넘긴다.

renderer.render("templates/admin/approval.vm", context, resp.getWriter());

 

render() 메서드는 2가지가 있다. Context를 넘기는 경우와 넘기지 않는 경우. 필요에 따라 넘기거나 넘기지 않거나 알아서 선택하면 된다. 

 

그리고 보통은 비즈니스 로직을 처리하기 위해 필요한 서비스를 주입받는다. 지금까지 작성한 내용은 쉽게 설명하기 위해 그런 내용을 다 뺐지만, 아마 대부분은 특정 서비스를 주입받아 사용할 것이다.

 

임의의 서비스가 아래처럼 있다고 가정해보자. 

public interface ApprovalManagementService {...}
@Slf4j
@Service
@RequiredArgsConstructor
public class ApprovalManagementServiceImpl implements ApprovalManagementService {...}

스프링 스캐너를 사용하고 있기 때문에 @Service 애노테이션으로 간단하게 이 서비스를 스캔할 수 있다.

그리고 주입받는것도 역시 스프링이랑 동일하게 이렇게 주입받으면 된다.

 

ApprovalManagementServlet

package kr.osci.kapproval.admin.servlet;

import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.sal.api.websudo.WebSudoManager;
import com.atlassian.templaterenderer.TemplateRenderer;
import kr.osci.kapproval.admin.service.ApprovalManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
public class ApprovalManagementServlet extends HttpServlet {

    private final ApprovalManagementService approvalManagementService;

    @ComponentImport private final TemplateRenderer renderer;

    @ComponentImport private final WebSudoManager webSudo;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        webSudo.willExecuteWebSudoRequest(req);
		
        ...
        
        renderer.render("templates/admin/approval.vm", context, resp.getWriter());
    }
}

그리고 이 서블릿을 역시나 마찬가지로 리소스로 등록해야 한다.

atlassian-plugin.xml

<servlet key="approvalManagementServlet" class="kr.osci.kapproval.admin.servlet.ApprovalManagementServlet">
    <url-pattern>/kapproval/admin/approval</url-pattern>
</servlet>

리소스로 등록할 때 url-pattern 이라는 태그가 있는데 이 태그는 이 서블릿을 호출하는 URL이라고 생각하면 된다.

이 URL이 위에서 등록했던 web-item 태그의 link 경로가 된다.

근데 서블릿은 기본 context path가 존재한다. 그 기본 context path가 `/plugins/servlet`이 된다.

그래서 web-itemlink가 이렇게 등록되는 것이다.

<web-section key="adminPluginsMenuSection" name="k-approval admin plugins menu section" location="admin_plugins_menu" >
    <label key="menu.admin.section"/>
</web-section>

<web-item key="approvalManagement" name="k-approval management" section="admin_plugins_menu/adminPluginsMenuSection" >
    <label key="menu.admin.section.approval"/>
    <link>/plugins/servlet/kapproval/admin/approval</link>
</web-item>

 

이런식으로 서블릿을 만들었으면 서블릿이 렌더링 할 뷰 템플릿이 있어야 한다. renderer.render()에서 파라미터로 작성한 경로 그대로 뷰 템플릿을 Velocity로 만들어서 사용하면 된다. 

src/main/resources/templates/admin/approval.vm

<html>
  <head>
    <title>My Admin</title>
  </head>
  <body>
    <form id="admin">
      <div>
        <label for="name">Name</label>
        <input type="text" id="name" name="name">
      </div>
      <div>
        <label for="age">Age</label>
        <input type="text" id="age" name="age">
      </div>
      <div>
        <input type="submit" value="Save">
      </div>
    </form>
  </body>
</html>

 

더해서, 이 뷰 템플릿에서 사용하고자 하는 자원이 있을 수 있다. 예를 들면 JS, CSS 파일 또는 JIRA Plugin 개발하면서 아주 많이 자주 사용되는 AUI 리소스들. 그런것들을 뷰 템플릿에서 사용하고 싶으면 이 또한 atlassian-plugin.xml 파일에 웹 리소스를 등록해서 그 리소스를 뷰 템플릿에서 가져와야 한다. 

atlassian-plugin.xml

<!-- admin approval management 화면 resources -->
<web-resource key="admin-approval-resources" name="admin approval resource">
    <!-- 화면 모듈 include -->
    <resource type="download" name="approval.css" location="/css/admin/approval.css"/>
    <resource type="download" name="approval.js" location="/js/admin/approval.js"/>

    <transformation extension="js">
        <transformer key="jsI18n"/>
    </transformation>

    <context>kapproval</context>
</web-resource>

이렇게 웹 리소스를 등록한다. 이 경우에는 CSS, JS 파일이 필요한것으로 보인다. 그리고 JS에서도 사용할 i18n 리소스까지 이렇게 웹 리소스를 등록을 한 상태에서 뷰 템플릿에서 가장 상단에 아래 코드를 추가한다.

$webResourceManager.requireResource("<atlassian-plugin-key>:<web-resource-key>")

 

`atlassian-plugin-key`atlassian-plugin.xml 파일에 가장 상단에 있는 key attribute 값을 말한다.

`web-resource-key`는 바로 위에 작성한 web-resource 태그의 key attribute값을 말한다.

 

`atlassian-plugin-key`atlassian-plugin.xml 파일에 가장 최상단에 보이는 atlassian-plugin 태그의 key

<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
        ...
</atlassian-plugin>

 

그리고 저 리소스를 추가한 최종 뷰 템플릿 코드는 이렇게 생겼다.

$webResourceManager.requireResource("com.atlassian.tutorial:admin-approval-resources")

<html>
  <head>
    <title>My Admin</title>
  </head>
  <body>
    <form id="admin">
      <div>
        <label for="name">Name</label>
        <input type="text" id="name" name="name">
      </div>
      <div>
        <label for="age">Age</label>
        <input type="text" id="age" name="age">
      </div>
      <div>
        <input type="submit" value="Save">
      </div>
    </form>
  </body>
</html>

 

정리

이게 큰 그림에서의 설정, 리소스 등록, 서비스를 주입하는 방법이다. 이제 그 내부의 비즈니스 로직은 요구사항에 맞게 원하는대로 구현하면 된다. 관리자 화면의 섹션 생성, 섹션 하단에 메뉴 등록, 메뉴에서 보여지는 화면을 호출하는 서블릿, 서블릿에서 주입하는 서비스까지 전부 알아보았다. 서비스 중에선 atlassian 모듈 하위의 서비스인 경우 @ComponentImport 애노테이션을 붙여서 임포트하는 것까지. 

 

다음엔 DB를 연동하고 데이터를 읽고 쓰는 방법까지 알아보자!

728x90
반응형
LIST

+ Recent posts