parent
41598ca440
commit
250e01a0c1
@ -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;
|
package kr.re.etri.autoflow.entity;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
// Entity (DB 저장용)
|
/**
|
||||||
|
* Kubeflow Run 정보를 저장하는 엔티티
|
||||||
|
* DB 테이블: tb_runs
|
||||||
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "tb_runs")
|
@Table(name = "tb_runs")
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class KubeflowRunEntity {
|
public class KubeflowRunEntity {
|
||||||
|
|
||||||
@Id
|
@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;
|
private String runId;
|
||||||
|
|
||||||
|
@Schema(description = "Run이 속한 Experiment ID", example = "exp-001")
|
||||||
private String experimentId;
|
private String experimentId;
|
||||||
|
|
||||||
|
@Schema(description = "Run의 표시 이름", example = "My First Run")
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
|
@Schema(description = "Run의 저장 상태 (예: AVAILABLE, ARCHIVED)", example = "AVAILABLE")
|
||||||
private String storageState;
|
private String storageState;
|
||||||
|
|
||||||
|
@Schema(description = "Run 설명", example = "This run tests the new pipeline")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "연결된 Pipeline ID", example = "pipeline-001")
|
||||||
private String pipelineId;
|
private String pipelineId;
|
||||||
|
|
||||||
|
@Schema(description = "연결된 Pipeline Version ID", example = "v1.0")
|
||||||
private String pipelineVersionId;
|
private String pipelineVersionId;
|
||||||
|
|
||||||
|
@Schema(description = "실행 서비스 계정", example = "default")
|
||||||
private String serviceAccount;
|
private String serviceAccount;
|
||||||
|
|
||||||
|
@Schema(description = "Run 생성 시각", example = "2025-09-24T08:30:00Z")
|
||||||
private Instant createdAt;
|
private Instant createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "Run 예약 시각", example = "2025-09-24T08:45:00Z")
|
||||||
private Instant scheduledAt;
|
private Instant scheduledAt;
|
||||||
|
|
||||||
|
@Schema(description = "Run 완료 시각", example = "2025-09-24T09:00:00Z")
|
||||||
private Instant finishedAt;
|
private Instant finishedAt;
|
||||||
|
|
||||||
|
@Schema(description = "Run 상태 (예: RUNNING, COMPLETED, FAILED)", example = "COMPLETED")
|
||||||
private String state;
|
private String state;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue