본문으로 건너뛰기
  1. Posts/

Polarion REST API - WorkItem Form Extension 구현하기

·7 분· loading · loading ·
Jeong
Solution Techtopic Polarion RESTAPI
RedBeanSoft
작성자
RedBeanSoft
Siemens 의 PLM/ALM/CAD 분야 전문 파트너입니다.
작성자
jeong
There is no end to learning, but there are many beginnings.

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 파일을 생성합니다.

1_vm파일생성

적용 테스트를 위해 myExtension.vm 파일을 열어 “Hello World!” 를 작성하고 저장합니다.

3_vm내용테스트

Velocity Form Extension을 적용하기 위해 Form Layout Configuration 페이지에 접근하여 원하는 필드 위치에 아래와 같이 입력 후 저장합니다.

<extension id="velocity_form" label="WorkItem 생성 Form 테스트" script="myExtension.vm" />

2_FormLayout적용

Form Extension이 적용된 것을 화면에서 확인할 수 있습니다.

4_FormLayout테스트

화면 구성하기
#

현재 열린 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” 문서로 이동합니다.

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

결과
#

잘 따라오셨나요?

6_Form결과

그럼 우리가 원하는 동작을 하는지 테스트할 시간입니다.

Title과 Description에 값을 입력하고 “Task 생성” 버튼을 눌러봅시다.

7_생성테스트

다음과 같이 Action의 상태가 변경됩니다.

8_ActionChange

개발자 도구(F12)를 열어 로그를 확인할 수 있습니다.

10_개발자도구

새로고침(F5)하면 생성된 Task 아이템의 정보와 다시 Task를 생성할 수 있는 새 행이 추가됩니다.

9_생성후

Task 아이템과 링크도 잘 걸렸습니다!

11_링크완료

https://polarion.code.blog/2023/08/03/polarion-rest-api-a-form-extension-example/#creating-the-createworkitem-function

관련 글

Polarion - PublicJS를 통해 문서 생성 버튼 만들기
·1 분· loading · loading
Jeong
Solution Polarion PublicJS

PublicJS 클래스에는 Live Report Pages, Plans, Test Runs, Documents, Classic Wiki Pages 에서 사용할 수 있는 public JavaScript 메서드가 정의되어 있습니다.

Polarion - Highcharts 구현 (2) Scripted Chart
·2 분· loading · loading
Jeong
Solution Polarion Highcharts

이 글은 Polarion 사용자 및 개발자를 위한 것으로, Scripted Chart 위젯을 사용하여 Highcharts를 구현하는 방법을 안내합니다.

LiveReport Page, Info Page, Plan에서 Scripted Chart 위젯을 사용하면 Highcharts의 모든 차트를 구현할 수 있습니다.

Polarion - Highcharts 구현 (1) Wiki Pages
·2 분· loading · loading
Jeong
Solution Polarion Highcharts

이 글은 Polarion 사용자 및 개발자를 위한 것으로, Polarion Wiki Pages에 Highcharts를 구현하는 방법을 안내합니다.

Polarion은 Highcharts 라이브러리를 통합하여 사용자가 위키 페이지에서 간편하게 다양한 차트를 생성할 수 있도록 했습니다.