[ADD] Kubeflow Run 검색 및 관리 기능 구현, 엔티티 확장 및 새로운 Controller, Service, Repository 추가

main
bjkim 9 months ago
parent 41598ca440
commit 250e01a0c1

@ -97,8 +97,8 @@ public class KubeflowRunBatchConfig {
@Bean
public ItemProcessor<KubeflowRunRequest, KubeflowRunEntity> runProcessor() {
return dto -> {
// DB에서 이미 존재하는 runId 체크
if (kubeflowRunRepository.existsById(dto.getRun_id())) {
// runId 기준 중복 체크
if (kubeflowRunRepository.existsByRunId(dto.getRun_id())) {
return null; // 중복이면 skip
}

@ -0,0 +1,54 @@
package kr.re.etri.autoflow.controllers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import kr.re.etri.autoflow.entity.KubeflowRunEntity;
import kr.re.etri.autoflow.payload.request.KubeflowRunSearchRequest;
import kr.re.etri.autoflow.repository.KubeflowRunRepository;
import kr.re.etri.autoflow.service.KubeflowRunService;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "Kubeflow Run", description = "Kubeflow Run 조회 API")
@RestController
@RequestMapping("/api/kubeflow/runs")
@RequiredArgsConstructor
public class KubeflowRunsNewController {
private final KubeflowRunRepository runRepository;
private final KubeflowRunService kubeflowRunService;
@Operation(summary = "모든 Kubeflow Run 조회")
@GetMapping
public ResponseEntity<List<KubeflowRunEntity>> getAllRuns() {
List<KubeflowRunEntity> runs = runRepository.findAll();
return ResponseEntity.ok(runs);
}
@Operation(summary = "Kubeflow Run 단건 조회")
@GetMapping("/{runId}")
public ResponseEntity<KubeflowRunEntity> getRun(
@Parameter(description = "Kubeflow Run ID", example = "ad980d7f-050a-4c59-a775-94394befad40")
@PathVariable("runId") String runId) {
return runRepository.findById(runId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@Operation(summary = "Kubeflow Run 검색 및 페이지네이션 조회")
@GetMapping("/search")
public ResponseEntity<Page<KubeflowRunEntity>> searchRuns(
@ParameterObject @ModelAttribute KubeflowRunSearchRequest request) {
Page<KubeflowRunEntity> page = kubeflowRunService.search(request);
return ResponseEntity.ok(page);
}
}

@ -1,34 +1,63 @@
package kr.re.etri.autoflow.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
// Entity (DB 저장용)
/**
* Kubeflow Run
* DB : tb_runs
*/
@Entity
@Table(name = "tb_runs")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KubeflowRunEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "자동 증가 내부 ID (DB 관리용)", example = "1")
private Long Id;
@Column(unique = true, nullable = false)
@Schema(description = "Kubeflow에서 제공하는 Run ID", example = "12345-abcdef")
private String runId;
@Schema(description = "Run이 속한 Experiment ID", example = "exp-001")
private String experimentId;
@Schema(description = "Run의 표시 이름", example = "My First Run")
private String displayName;
@Schema(description = "Run의 저장 상태 (예: AVAILABLE, ARCHIVED)", example = "AVAILABLE")
private String storageState;
@Schema(description = "Run 설명", example = "This run tests the new pipeline")
private String description;
@Schema(description = "연결된 Pipeline ID", example = "pipeline-001")
private String pipelineId;
@Schema(description = "연결된 Pipeline Version ID", example = "v1.0")
private String pipelineVersionId;
@Schema(description = "실행 서비스 계정", example = "default")
private String serviceAccount;
@Schema(description = "Run 생성 시각", example = "2025-09-24T08:30:00Z")
private Instant createdAt;
@Schema(description = "Run 예약 시각", example = "2025-09-24T08:45:00Z")
private Instant scheduledAt;
@Schema(description = "Run 완료 시각", example = "2025-09-24T09:00:00Z")
private Instant finishedAt;
@Schema(description = "Run 상태 (예: RUNNING, COMPLETED, FAILED)", example = "COMPLETED")
private String state;
}

@ -23,10 +23,10 @@ public class BaseSearchRequest {
@Schema(description = "검색 유형 (예: 전체, 제목, 작성자 등)", example = "전체")
private String searchType;
@Schema(description = "등록일자 검색 시작", example = "2025-08-01")
@Schema(description = "등록일자 검색 시작", example = "1970-08-01")
private String startDate;
@Schema(description = "등록일자 검색 종료", example = "2025-08-31")
@Schema(description = "등록일자 검색 종료", example = "2050-08-31")
private String endDate;
@Schema(description = "정렬 기준 필드명", defaultValue = "id", example = "id")

@ -0,0 +1,13 @@
package kr.re.etri.autoflow.payload.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class KubeflowRunSearchRequest extends BaseSearchRequest {
@Schema(description = "실험 ID", example = "1")
private String experimentId;
}

@ -2,6 +2,8 @@ package kr.re.etri.autoflow.repository;
import kr.re.etri.autoflow.entity.KubeflowRunEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface KubeflowRunRepository extends JpaRepository<KubeflowRunEntity, String> {
public interface KubeflowRunRepository extends JpaRepository<KubeflowRunEntity, String>, JpaSpecificationExecutor<KubeflowRunEntity> {
boolean existsByRunId(String runId);
}

@ -0,0 +1,38 @@
package kr.re.etri.autoflow.service;
import kr.re.etri.autoflow.entity.KubeflowRunEntity;
import kr.re.etri.autoflow.payload.request.KubeflowRunSearchRequest;
import kr.re.etri.autoflow.repository.KubeflowRunRepository;
import kr.re.etri.autoflow.specification.KubeflowRunSpecification;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class KubeflowRunService {
private final KubeflowRunRepository runRepository;
private final KubeflowRunSpecification runSpecification;
@Transactional(readOnly = true)
public Page<KubeflowRunEntity> search(KubeflowRunSearchRequest request) {
int pageIndex = request.getPage() > 0 ? request.getPage() - 1 : 0;
Pageable pageable = PageRequest.of(
pageIndex,
request.getSize(),
Sort.by(Sort.Direction.fromString(request.getSortDirection()), request.getSortField())
);
Specification<KubeflowRunEntity> spec = runSpecification.searchByConditions(
request.getExperimentId(), // experimentId는 필수
request.getSearchType(),
request.getKeyword()
);
return runRepository.findAll(spec, pageable);
}
}

@ -0,0 +1,146 @@
//package kr.re.etri.autoflow.specification;
//
//import jakarta.annotation.PostConstruct;
//import jakarta.persistence.EntityManager;
//import jakarta.persistence.metamodel.Attribute;
//import jakarta.persistence.metamodel.EntityType;
//import jakarta.persistence.metamodel.Metamodel;
//import kr.re.etri.autoflow.entity.KubeflowRunEntity;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.data.jpa.domain.Specification;
//import org.springframework.stereotype.Component;
//
//import jakarta.persistence.criteria.Predicate;
//import java.util.Set;
//import java.util.stream.Collectors;
//
//@Slf4j
//@Component
//public class KubeflowRunSpecification {
//
// private final EntityManager entityManager;
// private Set<String> stringFields;
//
// public KubeflowRunSpecification(EntityManager entityManager) {
// this.entityManager = entityManager;
// }
//
// @PostConstruct
// public void init() {
// Metamodel metamodel = entityManager.getMetamodel();
// EntityType<KubeflowRunEntity> entityType = metamodel.entity(KubeflowRunEntity.class);
//
// stringFields = entityType.getAttributes().stream()
// .filter(attr -> attr.getJavaType().equals(String.class))
// .map(Attribute::getName)
// .collect(Collectors.toSet());
//
// log.info("KubeflowRunEntity string fields: {}", stringFields);
// }
//
// public Specification<KubeflowRunEntity> searchByConditions(
// String experimentId, String searchType, String keyword) {
//
// return (root, query, cb) -> {
// Predicate predicate = cb.conjunction();
//
// // experimentId는 항상 조건으로 추가
// predicate = cb.and(predicate,
// cb.equal(root.get("experimentId"), experimentId));
//
// if (keyword != null && !keyword.isEmpty()) {
// if (searchType == null || searchType.isEmpty() ||
// "전체".equalsIgnoreCase(searchType) || "all".equalsIgnoreCase(searchType)) {
//
// Predicate orPredicate = cb.disjunction();
// for (String field : stringFields) {
// orPredicate = cb.or(orPredicate,
// cb.like(cb.lower(root.get(field)), "%" + keyword.toLowerCase() + "%"));
// }
// predicate = cb.and(predicate, orPredicate);
//
// } else if (stringFields.contains(searchType)) {
// predicate = cb.and(predicate,
// cb.like(cb.lower(root.get(searchType)), "%" + keyword.toLowerCase() + "%"));
// }
// }
//
// return predicate;
// };
// }
//}
package kr.re.etri.autoflow.specification;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.EntityType;
import jakarta.persistence.metamodel.Metamodel;
import kr.re.etri.autoflow.entity.ProjectEntity;
import kr.re.etri.autoflow.entity.KubeflowRunEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import jakarta.persistence.criteria.Predicate;
import java.time.LocalDate;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Component
public class KubeflowRunSpecification {
private final EntityManager entityManager;
private Set<String> stringFields;
public KubeflowRunSpecification(EntityManager entityManager) {
this.entityManager = entityManager;
}
// 스프링 빈 초기화 후 실행
@PostConstruct
public void init() {
Metamodel metamodel = entityManager.getMetamodel();
EntityType<KubeflowRunEntity> entityType = metamodel.entity(KubeflowRunEntity.class);
// 문자열 타입 필드명만 추출
stringFields = entityType.getAttributes().stream()
.filter(attr -> attr.getJavaType().equals(String.class))
.map(Attribute::getName)
.collect(Collectors.toSet());
log.info("KubeflowRunEntity string fields: {}", stringFields);
}
public Specification<KubeflowRunEntity> searchByConditions(
String experimentId, String searchType, String keyword) {
return (root, query, cb) -> {
Predicate predicate = cb.conjunction();
predicate = cb.and(predicate, cb.equal(root.get("experimentId"), experimentId));
if (keyword != null && !keyword.isEmpty()) {
if (searchType == null || searchType.isEmpty() ||
"전체".equalsIgnoreCase(searchType) || "all".equalsIgnoreCase(searchType)) {
Predicate orPredicate = cb.disjunction();
for (String field : stringFields) {
orPredicate = cb.or(orPredicate,
cb.like(cb.lower(root.get(field)), "%" + keyword.toLowerCase() + "%"));
}
predicate = cb.and(predicate, orPredicate);
} else if (stringFields.contains(searchType)) {
predicate = cb.and(predicate,
cb.like(cb.lower(root.get(searchType)), "%" + keyword.toLowerCase() + "%"));
}
}
return predicate;
};
}
}

@ -23,8 +23,8 @@ public class OpenAPIConfig {
private static final String SECURITY_SCHEME_ACCESS = "cuuva-jwt-cookie";
private static final String SECURITY_SCHEME_REFRESH = "cuuva-jwt-refresh-cookie";
private static final String PRODUCTION_SERVER_URL = "http://cuuva.com:2480/autoflow";
private static final String LOCAL_SERVER_URL = "http://localhost:80";
private static final String PRODUCTION_SERVER_URL = "http://cuuva.com:2481/autoflow";
private static final String LOCAL_SERVER_URL = "http://localhost:8080";
@Bean
public OpenAPI customOpenAPI() {

Loading…
Cancel
Save