Polarion REST API를 활용하여 WorkItem과 link 관계를 갖는 새 WorkItem을 생성하는 Form Extension을 생성해 보도록 하겠습니다.
이 포스팅에서는 Polarion REST API 개념 및 활성화 방법에 대한 자세한 내용은 다루지 않습니다. REST API에 관한 자세한 내용은 Polarion에서 제공하는 REST API User Guide for Polarion 문서를 참고해 주세요.
Polarion 내부에서 REST API를 사용하기 위해 다음 속성을 Polarion 서버에 설정해 두어야 합니다.
# REST API 활성화
com.siemens.polarion.rest.enabled=true
# 일회용 토큰 활성화 (X-Polarion-Rest-Token)
com.siemens.polarion.rest.security.restApiToken.enabled=true
# (선택) SwaggerUI 활성화
com.siemens.polarion.rest.swaggerUi.enabled=true
Velocity Form Extension 만들기 #
Velocity Form Extension을 만들기 위해 [Polarion_설치경로]/scripts
경로에 .vm 확장자 파일을 생성합니다.
예시를 위해 myExtension.vm
이름으로 vm 파일을 생성합니다.
적용 테스트를 위해 myExtension.vm
파일을 열어 “Hello World!” 를 작성하고 저장합니다.
Velocity Form Extension을 적용하기 위해 Form Layout Configuration 페이지에 접근하여 원하는 필드 위치에 아래와 같이 입력 후 저장합니다.
<extension id="velocity_form" label="WorkItem 생성 Form 테스트" script="myExtension.vm" />
Form Extension이 적용된 것을 화면에서 확인할 수 있습니다.
화면 구성하기 #
현재 열린 WorkItem은 $object
변수를 이용해 접근할 수 있습니다.
현재 WorkItem과 링크된 새 WorkItem 생성을 위해 아래의 코드를 myExtension.vm
파일에 적용합니다.
41번째 줄의 \#set($taskWorkItemType = "task")
코드는 생성하려는 WorkItem의 Type을 가리킵니다. 적용하려는 프로젝트에 따라 조정합니다.
## CSS
<style>
/* Style for the table */
.niceTable {
width: 100%;
border-collapse: collapse;
border: 1px solid #ddd;
}
/* Style for table headers */
.niceTh {
background-color: #f2f2f2;
color: #333;
font-weight: bold;
padding: 8px;
border: 1px solid #ddd;
}
/* Style for table cells */
.niceTd {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
}
/* Alternate row colors for better readability */
niceTr:nth-child(even) {
background-color: #f2f2f2;
text-align: center;
}
/* Hover effect on rows */
niceTr:hover {
background-color: #ddd;
}
</style>
## 현재 워크아이템, 구성
#set($workItemId = $object.getId())
#set($projectId = $object.getProjectId())
#set($taskWorkItemType = "task") ## 이 라인을 프로젝트에 따라 조정합니다.
#set($renderingWorkItem = $transaction.workItems().getBy().oldApiObject($object))
## 연결된 모든 Task를 가져오기 위한 SQL
#set($linkedWorkItemsQuery = "SQL:(select WORKITEM.C_URI from WORKITEM inner join PROJECT on PROJECT.C_URI = WORKITEM.FK_URI_PROJECT inner join STRUCT_WORKITEM_LINKEDWORKITEMS as SWL on WORKITEM.C_URI = SWL.FK_URI_P_WORKITEM inner join WORKITEM as WORKITEM2 on WORKITEM2.C_URI = SWL.FK_WORKITEM where PROJECT.C_ID = '$projectId' AND WORKITEM.C_TYPE = 'task' AND WORKITEM2.C_ID = '$workItemId')")
#set($linkedTasks = $trackerService.queryWorkItems($linkedWorkItemsQuery, "created"))
<div class="table-wrapper">
<table id="subTaskTable" class="niceTable">
<tr class="niceTr">
<th class="niceTh">ID</th>
<th class="niceTh">Title</th>
<th class="niceTh">Description</th>
<th class="niceTh">Status</th>
<th class="niceTh">Action</th>
</tr>
#foreach($linkedWorkItem in $linkedTasks)
<tr class="niceTr">
<td class="niceTd">$!linkedWorkItem.getId()</td>
<td class="niceTd">$!linkedWorkItem.getTitle()</td>
<td class="niceTd">$transaction.workItems().getBy().oldApiObject($linkedWorkItem).fields().description().render().htmlFor().forFrame()</td>
<td class="niceTd">$!linkedWorkItem.getStatus().getName()</td>
<td class="niceTd">Already Existing</td>
</tr>
#end
<tr class="niceTr">
<td class="niceTd">-</td>
<td class="niceTd"><input type="text" id="inputFieldTitle"
placeholder="여기에 Title을 입력하세요." /></td>
<td class="niceTd"><input type="text" id="inputFieldDescription"
placeholder="여기에 Description을 입력하세요." /></td>
<td class="niceTd">-</td>
<td class="niceTd"><button>Task 생성</button></td>
</tr>
</table>
</div>
REST API 사용하기 #
Polarion REST API 리소스는 REST DOC 문서에서 확인할 수 있습니다.
우리는 새 WorkItem 만들기 위해 POST 요청을 생성해야 합니다. REST DOC 중 “postWorkItems” 문서로 이동합니다.
createWorkItem() #
Task 워크아이템 생성을 위한 코드입니다.
myExtension.vm
파일에 아래 코드를 작성하고 화면에서 결과를 확인합니다.
## 현재 워크아이템, 구성
#set($workItemId = $object.getId())
#set($projectId = $object.getProjectId())
<script>
var polarionRestApiToken = window.getRestApiToken(); // RestApi 일회성 토큰
var contextWorkItemId = "$workItemId";
var projectId = "$projectId";
// 워크아이템 생성 비동기 함수
async function createWorkItem() {
let createWorkItemTitle = clickedButton.getAttribute("data-title");
let createWorkItemDescription = clickedButton.getAttribute("data-description");
const url = 'http://localhost/polarion/rest/v1/projects/' + projectId + '/workitems'; // url 생성, 개발환경에 따라 url 주소 조정
const requestBody = {
data: [
{
type: 'workitems',
attributes: {
type: 'task',
description: {
type: 'text/html',
value: '테스트 Description'
},
title: '테스트 Title'
}
}
]
}; // body 생성
const options = {
method: "POST",
headers: {
'X-Polarion-REST-Token': polarionRestApiToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(requestBody)
}; // options 매개변수에 body 추가, X-Polarion-Rest-Token 사용
// fetch(url, options)를 통해 워크아이템 생성, 응답에 따라 로그 출력
try {
const response = await fetch(url, options);
if (response.ok) {
const responseBody = await response.json();
console.log("Task 생성 성공:", responseBody);
} else {
console.log("Task 생성 실패:", response.status);
}
} catch (error) {
console.error("Task 생성 오류:", error);
}
}
</script>
<td class="niceTd" onclick="createWorkItem()"><button>Task 생성</button></td>
응답에 따른 로그는 개발자 도구(F12)를 오픈하여 확인할 수 있습니다.
linkWorkItem() #
이제 새로 생성한 Task 워크아이템을 현재 워크아이템과 link 관계를 갖도록 해보겠습니다.
REST API를 통해 link하기 위해서는 “프로젝트 ID + 워크아이템 ID” 형태의 ID로 작업해야 합니다.
워크아이템 링크를 위한 코드입니다.
// 워크아이템 링크 비동기 함수
async function linkWorkItemAfterwards(sourceWorkItemId, targetWorkItemId) {
const urlLinkWorkItems = 'http://localhost/polarion/rest/v1/projects/' + projectId + '/workitems/' + sourceWorkItemId + '/linkedworkitems';
const requestBody = {
data: [
{
type: 'linkedworkitems',
attributes: {
role: 'relates_to'
},
relationships: {
workItem: {
data: {
type: "workitems",
id: projectId + '/' + targetWorkItemId
}
}
}
}
]
};
try {
const response = await fetch(urlLinkWorkItems, {
method: "POST",
headers: {
'X-Polarion-REST-Token': polarionRestApiToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(requestBody)
});
if (response.ok) {
const responseBody = await response.json();
console.log("링크 생성 성공:", responseBody);
} else {
console.log("링크 생성 실패:", response.status);
}
} catch (error) {
console.error("링크 생성 오류:", error);
}
}
전체 코드 #
이제 모든 코드를 결합하고 다음을 추가합니다.
- 사용자가 Title과 Description을 직접 입력
- Loading 애니메이션 추가
- 생성 및 연결을 차례대로 실행하는 createAndLinkTask() 함수 생성
아래는 이를 모두 반영한 전체 코드입니다.
myExtension.vm
파일에 이 코드를 작성하여 결과 화면을 출력해봅시다.
## 현재 워크아이템, 구성
#set($workItemId = $object.getId())
#set($projectId = $object.getProjectId())
#set($workItemTitle = $object.getTitle())
#set($taskWorkItemType = "task")
## CSS
<style>
/* Style for the table */
.niceTable {
width: 100%;
border-collapse: collapse;
border: 1px solid #ddd;
}
/* Style for table headers */
.niceTh {
background-color: #f2f2f2;
color: #333;
font-weight: bold;
padding: 8px;
border: 1px solid #ddd;
}
/* Style for table cells */
.niceTd {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
}
/* Alternate row colors for better readability */
niceTr:nth-child(even) {
background-color: #f2f2f2;
text-align: center;
}
/* Hover effect on rows */
niceTr:hover {
background-color: #ddd;
}
</style>
<script>
var polarionRestApiToken = window.getRestApiToken(); // RestApi 일회성 토큰
var contextWorkItemId = "$workItemId";
var projectId = "$projectId";
// 워크아이템 생성 비동기 함수
async function createWorkItem(workItemTitle, workItemDescription) {
const url = 'http://localhost/polarion/rest/v1/projects/' + projectId + '/workitems'; // url 생성, 개발환경에 따라 url 주소 조정
const requestBody = {
data: [
{
type: 'workitems',
attributes: {
type: 'task',
description: {
type: 'text/html',
value: workItemDescription
},
title: workItemTitle
}
}
]
}; // body 생성
const options = {
method: "POST",
headers: {
'X-Polarion-REST-Token': polarionRestApiToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(requestBody)
}; // options 매개변수에 body 추가, X-Polarion-Rest-Token 사용
// fetch(url, options)를 통해 워크아이템 생성, 응답에 따라 로그 출력
try {
const response = await fetch(url, options);
if (response.ok) {
const responseBody = await response.json();
console.log("Task 생성 성공:", responseBody);
return responseBody;
} else {
console.log("Task 생성 실패:", response.status);
}
} catch (error) {
console.error("Task 생성 오류:", error);
}
}
// 워크아이템 링크 비동기 함수
async function linkWorkItem(sourceWorkItemId, targetWorkItemId) {
const urlLinkWorkItems = 'http://localhost/polarion/rest/v1/projects/' + projectId + '/workitems/' + sourceWorkItemId + '/linkedworkitems';
const requestBody = {
data: [
{
type: 'linkedworkitems',
attributes: {
role: 'relates_to'
},
relationships: {
workItem: {
data: {
type: "workitems",
id: projectId + '/' + targetWorkItemId
}
}
}
}
]
};
try {
const response = await fetch(urlLinkWorkItems, {
method: "POST",
headers: {
'X-Polarion-REST-Token': polarionRestApiToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(requestBody)
});
if (response.ok) {
const responseBody = await response.json();
console.log("링크 생성 성공:", responseBody);
} else {
console.log("링크 생성 실패:", response.status);
}
} catch (error) {
console.error("링크 생성 오류:", error);
}
}
// Task 생성 및 링크
async function createAndLinkTask() {
let clickedButton = event.target;
clickedButton.disabled = true;
clickedButton.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Loading...';
let createWorkItemTitle = document.getElementById('inputFieldTitle').value;
let createWorkItemDescription = document.getElementById('inputFieldDescription').value;
let createdWorkItemResponse = await createWorkItem(createWorkItemTitle, createWorkItemDescription);
await linkWorkItem(createdWorkItemResponse.data[0].id.split("/")[1], contextWorkItemId);
clickedButton.innerHTML = "Task가 생성되었습니다.";
}
</script>
## Main
#set($renderingWorkItem = $transaction.workItems().getBy().oldApiObject($object))
## 연결된 모든 Task를 가져오기 위한 SQL
#set($linkedWorkItemsQuery = "SQL:(select WORKITEM.C_URI from WORKITEM inner join PROJECT on PROJECT.C_URI = WORKITEM.FK_URI_PROJECT inner join STRUCT_WORKITEM_LINKEDWORKITEMS as SWL on WORKITEM.C_URI = SWL.FK_URI_P_WORKITEM inner join WORKITEM as WORKITEM2 on WORKITEM2.C_URI = SWL.FK_WORKITEM where PROJECT.C_ID = '$projectId' AND WORKITEM.C_TYPE = 'task' AND WORKITEM2.C_ID = '$workItemId')")
#set($linkedTasks = $trackerService.queryWorkItems($linkedWorkItemsQuery, "created"))
<div class="table-wrapper">
<table id="subTaskTable" class="niceTable">
<tr class="niceTr">
<th class="niceTh">ID</th>
<th class="niceTh">Title</th>
<th class="niceTh">Description</th>
<th class="niceTh">Status</th>
<th class="niceTh">Action</th>
</tr>
#foreach($linkedWorkItem in $linkedTasks)
<tr class="niceTr">
<td class="niceTd">$!linkedWorkItem.getId()</td>
<td class="niceTd">$!linkedWorkItem.getTitle()</td>
<td class="niceTd">$transaction.workItems().getBy().oldApiObject($linkedWorkItem).fields().description().render().htmlFor().forFrame()</td>
<td class="niceTd">$!linkedWorkItem.getStatus().getName()</td>
<td class="niceTd">Already Existing</td>
</tr>
#end
<tr class="niceTr">
<td class="niceTd">-</td>
<td class="niceTd"><input type="text" id="inputFieldTitle" placeholder="여기에 Title을 입력하세요." /></td>
<td class="niceTd"><input type="text" id="inputFieldDescription" placeholder="여기에 Description을 입력하세요." /></td>
<td class="niceTd">-</td>
<td class="niceTd" onclick="createAndLinkTask()"><button>Task 생성</button></td>
</tr>
</table>
</div>
결과 #
잘 따라오셨나요?
그럼 우리가 원하는 동작을 하는지 테스트할 시간입니다.
Title과 Description에 값을 입력하고 “Task 생성” 버튼을 눌러봅시다.
다음과 같이 Action의 상태가 변경됩니다.
개발자 도구(F12)를 열어 로그를 확인할 수 있습니다.
새로고침(F5)하면 생성된 Task 아이템의 정보와 다시 Task를 생성할 수 있는 새 행이 추가됩니다.
Task 아이템과 링크도 잘 걸렸습니다!