본문으로 건너뛰기

Spark Workbench 사용자 정의 필드 타입 플러그인 개발 가이드

이 문서는 Spark Workbench 작업 필드 타입을 확장하려는 개발자를 위한 가이드입니다. 서드파티 플러그인도 Spark 기본 플러그인처럼 필드 타입 정의, 시스템 필드 정의, 필드 설정 로직, TQL 연산자, 대응되는 프론트엔드 Vue 컴포넌트를 독립적으로 제공할 수 있어야 합니다.

기본 필드 플러그인은 다음 위치에 있습니다.

spark-plugins/spark-workbench-default-plugin/

Text, Rich Text, Markdown, User, Attachment 같은 Spark 내장 필드 타입과 Title, Assignee, Description 같은 시스템 필드는 이 플러그인을 통해 등록됩니다. 플랫폼 호스트는 확장점만 소비하며, 구체적인 필드 타입을 작업 디자이너에 하드코딩하지 않습니다.

1. 필드 플러그인 모델​

필드 타입 플러그인은 보통 두 부분으로 구성됩니다.

  • 백엔드 확장점: Lattice Ability를 통해 필드 타입, 시스템 필드, 필드 설정 폼, 지원하는 TQL 연산자, 필터 프로토콜을 반환합니다.
  • 프론트엔드 컴포넌트: Vue 컴포넌트로 생성 / 편집 상태의 컨트롤을 렌더링하며, 읽기 전용 및 작업 상세 컴포넌트도 제공할 수 있습니다.

권장 구조:

my-task-field-plugin/
pom.xml
src/main/java/
.../MyTaskFieldPlugin.java
.../MyFieldRegistryExt.java
.../PriorityFieldTypeProvider.java
src/main/frontend/
package.json
vite.config.js
src/
index.js
PriorityField.vue
PriorityDetailField.vue

플러그인 jar에는 Java 확장점과 프론트엔드 정적 리소스가 함께 포함되어야 하며, Spark가 런타임에 로드합니다.

2. 비즈니스 식별자​

플러그인은 자신의 Lattice 비즈니스 식별자를 선언해야 합니다.

@Business(code = "my.task.field.plugin", name = "My Task Field Plugin")
public class MyTaskFieldPlugin extends BusinessTemplate {
}

필드 등록 시 Spark는 현재 Lattice에 등록된 모든 비즈니스 식별자를 순회하며 각 식별자에 대해 필드 등록 Ability를 호출합니다. 서드파티 플러그인은 @Realization을 플랫폼 내장 code에 연결하지 말고, 자신의 플러그인 비즈니스 식별자를 사용해야 합니다.

3. 백엔드 필드 타입 등록​

필드 등록 확장점은 SparkEraTaskFieldRegistryExt이며, 일반적으로 빈 구현을 상속합니다.

@Realization(codes = "my.task.field.plugin")
public class MyFieldRegistryExt extends BlankSparkEraTaskFieldRegistryExt {

private static final String FRONTEND_RESOURCE =
"/spark-gadgets/my-task-field-plugin/index.umd.js";

@Override
public List<SparkEraTaskCustomFieldTypeProvider> listCustomFieldTypeProviders(
SparkEraTaskFieldRegistryContext context) {
return List.of(new PriorityFieldTypeProvider(FRONTEND_RESOURCE));
}
}

필드 타입 Provider 예시:

public class PriorityFieldTypeProvider implements SparkEraTaskCustomFieldTypeProvider {

private final String frontendResource;

public PriorityFieldTypeProvider(String frontendResource) {
this.frontendResource = frontendResource;
}

@Override
public String getCode() {
return "priority_score";
}

@Override
public String getTitle() {
return "Priority Score";
}

@Override
public SparkEraTaskFieldCategory getCategory() {
return SparkEraTaskFieldCategory.BASIC;
}

@Override
public String getDescription() {
return "A numeric priority score with visual emphasis.";
}

@Override
public String getComponentType() {
return "priorityScore";
}

@Override
public String getTaskFieldType() {
return "number";
}

@Override
public String getPreviewIcon() {
return "gauge";
}

@Override
public boolean isSortable() {
return true;
}

@Override
public List<String> supportOperators() {
return List.of("eq", "neq", "gt", "ge", "lt", "le");
}

@Override
public String getFrontendResource() {
return frontendResource;
}

@Override
public String getEditComponent() {
return "my$field-priority-score";
}

@Override
public String getDetailComponent() {
return "my$task-detail-priority-score-field";
}
}

주요 필드:

  • code: 전역에서 고유해야 하는 필드 타입 코드.
  • componentType: 런타임 컨트롤의 의미.
  • taskFieldType: 기본 값 타입으로, 조회, 인덱싱, 표시 방식에 영향을 줍니다.
  • previewIcon: 작업 디자이너 필드 팔레트 아이콘.
  • supportOperators: 이 필드가 지원하는 TQL 연산자입니다. 전체 연산자 집합을 기본으로 반환하지 마세요.
  • frontendResource: 플러그인 프론트엔드 UMD 엔트리.
  • editComponent: 생성 / 편집 폼용 Vue 컴포넌트 key.
  • readonlyComponent: 일반 읽기 전용 폼용 Vue 컴포넌트 key이며 선택 사항입니다.
  • detailComponent: 작업 상세 렌더 프로토콜 컴포넌트 key.

4. 시스템 필드 등록​

시스템 필드는 SparkEraTaskSystemFieldProvider를 사용합니다.

@Override
public List<SparkEraTaskSystemFieldProvider> listSystemFieldProviders(
SparkEraTaskFieldRegistryContext context) {
return List.of(new EscalationLevelSystemFieldProvider(FRONTEND_RESOURCE));
}

시스템 필드 Provider는 안정적인 code, 표시 label, 필드 타입, 목록 표시 가능 여부, 검색 가능 여부, 정렬 가능 여부, 지원 연산자를 반환해야 합니다. 시스템 필드도 frontendResource와 컴포넌트 key를 선언할 수 있습니다.

5. 프론트엔드 컴포넌트 등록​

플러그인 프론트엔드 엔트리는 호스트가 노출하는 window.__SPARK_TASK_FIELDS__를 통해 컴포넌트를 등록합니다.

import PriorityField from "./PriorityField.vue";
import PriorityDetailField from "./PriorityDetailField.vue";

const api = typeof window === "undefined" ? null : window.__SPARK_TASK_FIELDS__;

if (api?.registerFieldComponent) {
api.registerFieldComponent("my$field-priority-score", PriorityField);
}

if (api?.registerDetailFieldComponent) {
api.registerDetailFieldComponent("my$task-detail-priority-score-field", PriorityDetailField);
}

생성 / 편집 컴포넌트가 받는 주요 props:

defineProps({
modelValue: [String, Number, Boolean, Array, Object],
field: Object,
definition: Object,
placeholder: String,
disabled: Boolean,
context: Object
});

필드 값을 다시 쓰려면 emit("update:modelValue", value)를 사용합니다. context에는 t, apiFetch, openUserSelect, chooseAttachmentFiles 같은 호스트 기능이 포함됩니다. 서드파티 컴포넌트는 공통 플랫폼 상호작용을 다시 구현하지 말고 호스트 기능을 우선 재사용해야 합니다.

작업 상세 컴포넌트는 Spark 렌더 프로토콜에 연결되며 componentModel, renderContext, t를 받습니다.

<template>
<div class="priority-detail">
<span>{{ field.label }}</span>
<strong>{{ field.value }}</strong>
</div>
</template>

<script setup>
import { computed } from "vue";

const props = defineProps({
componentModel: Object
});

const field = computed(() => props.componentModel?.props?.field || {});
</script>

6. Vite 빌드​

필드 플러그인 프론트엔드는 Gadget 플러그인과 동일하게 Vite library 모드를 사용하고 Vue를 external로 지정합니다.

import { resolve } from "node:path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
plugins: [vue()],
build: {
outDir: resolve(__dirname, "../../../target/classes/META-INF/resources/spark-gadgets/my-task-field-plugin"),
lib: {
entry: resolve(__dirname, "src/index.js"),
name: "MyTaskFieldPlugin",
formats: ["umd"],
fileName: () => "index.umd.js",
cssFileName: "index"
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "__SPARK_VUE__"
}
}
}
}
});

빌드 후 런타임 리소스 URL은 다음과 같습니다.

/spark-gadgets/my-task-field-plugin/index.umd.js

7. 개발 체크리스트​

  • 필드 타입 코드와 시스템 필드 코드는 안정적이어야 합니다.
  • @Realization(codes = "...")는 반드시 플러그인 자신의 비즈니스 식별자를 사용해야 합니다.
  • 조회 연산자는 필드 값 타입과 일치해야 하며, 모든 연산자를 반환하면 안 됩니다.
  • 정렬 가능 여부는 필드 타입이 명확히 선언해야 합니다. 리치 텍스트, 첨부파일, 긴 텍스트는 일반적으로 정렬에 적합하지 않습니다.
  • 프론트엔드 컴포넌트는 별도의 Vue 런타임을 번들링하지 않아야 합니다.
  • 컴포넌트 스타일은 Spark 테마 변수를 사용하고, 단일 테마 색상을 하드코딩하지 마세요.
  • 필드 label, description, 설정 항목 문구는 백엔드 i18n 메커니즘을 통해 반환해야 합니다.