Skip to content

Commit

Permalink
MODBULKOPS-43 - FQM Integration - FQM Results handling
Browse files Browse the repository at this point in the history
  • Loading branch information
siarhei-charniak committed Jan 30, 2024
1 parent 0b76a12 commit 93c07c6
Show file tree
Hide file tree
Showing 20 changed files with 564 additions and 25 deletions.
27 changes: 26 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
"modulePermissions": [
]
},
{
"methods": [ "POST" ],
"pathPattern": "/bulk-operations/query",
"permissionsRequired": [ "bulk-operations.item.query.post" ],
"modulePermissions": [
"fqm.entityTypes.item.get",
"fqm.query.async.post"
]
},
{
"methods": [ "POST" ],
"pathPattern": "/bulk-operations/{operationId}/content-update",
Expand Down Expand Up @@ -164,6 +173,8 @@
"pathPattern": "/bulk-operations/{operationId}",
"permissionsRequired": [ "bulk-operations.item.get" ],
"modulePermissions": [
"fqm.query.sync.get",
"fqm.query.async.results.get"
]
},
{
Expand Down Expand Up @@ -239,6 +250,11 @@
"displayName" : "upload identifiers list to initiate bulk-operation",
"description" : "Upload identifiers list to initiate bulk-operation"
},
{
"permissionName" : "bulk-operations.item.query.post",
"displayName" : "trigger bulk edit by query",
"description" : "Trigger bulk edit by query"
},
{
"permissionName" : "bulk-operations.item.content-update.post",
"displayName" : "upload content updates for bulk operation",
Expand Down Expand Up @@ -316,7 +332,8 @@
"bulk-operations.download.item.get",
"bulk-operations.list-users.collection.get",
"bulk-operations.files.item.delete",
"bulk-operations.item.cancel.post"
"bulk-operations.item.cancel.post",
"bulk-operations.item.query.post"
]
}
],
Expand Down Expand Up @@ -416,6 +433,14 @@
{
"id": "instance-formats",
"version": "2.0"
},
{
"id": "fqm-query",
"version": "1.0"
},
{
"id": "entity-types",
"version": "1.0"
}
],
"launchDescriptor": {
Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<feign-jackson.version>12.1</feign-jackson.version>
<aws-sdk-java.version>2.19.2</aws-sdk-java.version>
<hypersistence-utils-hibernate-60.version>3.5.0</hypersistence-utils-hibernate-60.version>
<lib-fqm-query-processor.version>1.1.0-SNAPSHOT</lib-fqm-query-processor.version>
<!-- Test dependencies versions -->
<testcontainer.version>1.17.6</testcontainer.version>
<wiremock.version>2.35.0</wiremock.version>
Expand Down Expand Up @@ -79,6 +80,12 @@
<version>${folio-spring-cql.version}</version>
</dependency>

<dependency>
<groupId>org.folio</groupId>
<artifactId>lib-fqm-query-processor</artifactId>
<version>${lib-fqm-query-processor.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
Expand Down Expand Up @@ -332,6 +339,7 @@
<generateSupportingFiles>true</generateSupportingFiles>
<supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
<generateModelDocumentation>true</generateModelDocumentation>
<schemaMappings>SubmitQuery=org.folio.querytool.domain.dto.SubmitQuery</schemaMappings>
<configOptions>
<java8>true</java8>
<dateLibrary>java</dateLibrary>
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/folio/bulkops/client/EntityTypeClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.folio.bulkops.client;

import org.folio.querytool.domain.dto.EntityType;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;

import java.util.UUID;

@FeignClient(name = "entity-types")
public interface EntityTypeClient {
@GetMapping("/{entityTypeId}")
EntityType getEntityType(@RequestHeader UUID entityTypeId);
}
27 changes: 27 additions & 0 deletions src/main/java/org/folio/bulkops/client/QueryClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.folio.bulkops.client;

import org.folio.querytool.domain.dto.QueryDetails;
import org.folio.querytool.domain.dto.QueryIdentifier;
import org.folio.querytool.domain.dto.SubmitQuery;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
import java.util.UUID;

@FeignClient(name = "query")
public interface QueryClient {

@PostMapping("")
QueryIdentifier executeQuery(@RequestBody SubmitQuery submitQuery);

@GetMapping("/{queryId}")
QueryDetails getQuery(@RequestHeader UUID queryId);

@GetMapping("/{queryId}/sortedIds")
List<UUID> getSortedIds(@RequestHeader UUID queryId, @RequestParam Integer offset, @RequestParam Integer limit);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.folio.bulkops.service.LogFilesService;
import org.folio.bulkops.service.PreviewService;
import org.folio.bulkops.service.RuleService;
import org.folio.querytool.domain.dto.SubmitQuery;
import org.folio.spring.cql.JpaCqlRepository;
import org.folio.spring.data.OffsetRequest;
import org.springframework.core.io.ByteArrayResource;
Expand Down Expand Up @@ -170,4 +171,9 @@ public ResponseEntity<Void> cancelOperationById(UUID operationId) {
bulkOperationService.cancelOperationById(operationId);
return new ResponseEntity<>(HttpStatus.OK);
}

@Override
public ResponseEntity<BulkOperationDto> triggerBulkEditByQuery(UUID xOkapiUserId, SubmitQuery submitQuery) {
return new ResponseEntity<>(bulkOperationMapper.mapToDto(bulkOperationService.triggerByQuery(xOkapiUserId, submitQuery)), HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ public class BulkOperation {
private LocalDateTime endTime;
private String errorMessage;
private boolean expired;
private UUID fqlQueryId;
private String fqlQuery;
}
59 changes: 44 additions & 15 deletions src/main/java/org/folio/bulkops/service/BulkOperationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.folio.bulkops.domain.dto.ApproachType.IN_APP;
import static org.folio.bulkops.domain.dto.ApproachType.MANUAL;
import static org.folio.bulkops.domain.dto.ApproachType.QUERY;
import static org.folio.bulkops.domain.dto.BulkOperationStep.UPLOAD;
import static org.folio.bulkops.domain.dto.OperationStatusType.APPLY_CHANGES;
import static org.folio.bulkops.domain.dto.OperationStatusType.COMPLETED;
import static org.folio.bulkops.domain.dto.OperationStatusType.COMPLETED_WITH_ERRORS;
import static org.folio.bulkops.domain.dto.OperationStatusType.DATA_MODIFICATION;
import static org.folio.bulkops.domain.dto.OperationStatusType.EXECUTING_QUERY;
import static org.folio.bulkops.domain.dto.OperationStatusType.FAILED;
import static org.folio.bulkops.domain.dto.OperationStatusType.NEW;
import static org.folio.bulkops.domain.dto.OperationStatusType.RETRIEVING_RECORDS;
import static org.folio.bulkops.domain.dto.OperationStatusType.REVIEW_CHANGES;
import static org.folio.bulkops.domain.dto.OperationStatusType.SAVED_IDENTIFIERS;
import static org.folio.bulkops.domain.dto.OperationStatusType.SAVING_RECORDS_LOCALLY;
import static org.folio.bulkops.util.Constants.FIELD_ERROR_MESSAGE_PATTERN;
import static org.folio.bulkops.util.Utils.resolveEntityClass;
Expand Down Expand Up @@ -66,6 +68,7 @@
import org.folio.bulkops.repository.BulkOperationExecutionRepository;
import org.folio.bulkops.repository.BulkOperationRepository;
import org.folio.bulkops.util.Utils;
import org.folio.querytool.domain.dto.SubmitQuery;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
Expand Down Expand Up @@ -102,8 +105,9 @@ public class BulkOperationService {
private final DataProcessorFactory dataProcessorFactory;
private final ErrorService errorService;
private final LogFilesService logFilesService;
private final NoteTableUpdater noteTableUpdater;
private final RecordUpdateService recordUpdateService;
private final EntityTypeService entityTypeService;
private final QueryService queryService;

private static final int OPERATION_UPDATING_STEP = 100;
private static final String PREVIEW_JSON_PATH_TEMPLATE = "%s/json/%s-Updates-Preview-%s.json";
Expand Down Expand Up @@ -172,6 +176,22 @@ public BulkOperation uploadCsvFile(EntityType entityType, IdentifierType identif
return bulkOperationRepository.save(operation);
}

public BulkOperation triggerByQuery(UUID userId, SubmitQuery submitQuery) {
var queryId = queryService.executeQuery(submitQuery);
var entityType = entityTypeService.getEntityTypeById(submitQuery.getEntityTypeId());
return bulkOperationRepository.save(BulkOperation.builder()
.id(UUID.randomUUID())
.entityType(entityType)
.approach(QUERY)
.identifierType(IdentifierType.ID)
.status(EXECUTING_QUERY)
.startTime(LocalDateTime.now())
.userId(userId)
.fqlQuery(submitQuery.getFqlQuery())
.fqlQueryId(queryId)
.build());
}

public void confirm(BulkOperation operation) {

operation.setProcessedNumOfRecords(0);
Expand Down Expand Up @@ -415,7 +435,7 @@ public BulkOperation startBulkOperation(UUID bulkOperationId, UUID xOkapiUserId,

private String executeDataExportJob(BulkOperationStep step, ApproachType approach, BulkOperation operation, String errorMessage) {
try {
if (NEW.equals(operation.getStatus())) {
if (Set.of(NEW, SAVED_IDENTIFIERS).contains(operation.getStatus())) {
if (MANUAL != approach) {
var job = dataExportSpringClient.upsertJob(Job.builder()
.type(ExportType.BULK_EDIT_IDENTIFIERS)
Expand Down Expand Up @@ -520,20 +540,29 @@ public void clearOperationProcessing(BulkOperation operation) {

public BulkOperation getOperationById(UUID bulkOperationId) {
var operation = getBulkOperationOrThrow(bulkOperationId);
if (DATA_MODIFICATION.equals(operation.getStatus())) {
var processing = dataProcessingRepository.findByBulkOperationId(bulkOperationId);
if (processing.isPresent() && StatusType.ACTIVE.equals(processing.get().getStatus())) {
operation.setProcessedNumOfRecords(processing.get().getProcessedNumOfRecords());
return operation;
return switch (operation.getStatus()) {
case EXECUTING_QUERY -> queryService.checkQueryExecutionStatus(operation);
case SAVED_IDENTIFIERS -> startBulkOperation(operation.getId(), operation.getUserId(), new BulkOperationStart()
.step(UPLOAD)
.approach(IN_APP)
.entityType(operation.getEntityType())
.entityCustomIdentifierType(IdentifierType.ID));
case DATA_MODIFICATION -> {
var processing = dataProcessingRepository.findByBulkOperationId(bulkOperationId);
if (processing.isPresent() && StatusType.ACTIVE.equals(processing.get().getStatus())) {
operation.setProcessedNumOfRecords(processing.get().getProcessedNumOfRecords());
}
yield operation;
}
} else if (APPLY_CHANGES.equals(operation.getStatus())) {
var execution = executionRepository.findByBulkOperationId(bulkOperationId);
if (execution.isPresent() && StatusType.ACTIVE.equals(execution.get().getStatus())) {
operation.setProcessedNumOfRecords(execution.get().getProcessedRecords());
return operation;
case APPLY_CHANGES -> {
var execution = executionRepository.findByBulkOperationId(bulkOperationId);
if (execution.isPresent() && StatusType.ACTIVE.equals(execution.get().getStatus())) {
operation.setProcessedNumOfRecords(execution.get().getProcessedRecords());
}
yield operation;
}
}
return operation;
default -> operation;
};
}

public BulkOperation getBulkOperationOrThrow(UUID operationId) {
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/folio/bulkops/service/EntityTypeService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.folio.bulkops.service;

import lombok.RequiredArgsConstructor;
import org.folio.bulkops.client.EntityTypeClient;
import org.folio.bulkops.domain.dto.EntityType;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
@RequiredArgsConstructor
public class EntityTypeService {
private final EntityTypeClient entityTypeClient;

public EntityType getEntityTypeById(UUID entityTypeId) {
var alias = entityTypeClient.getEntityType(entityTypeId).getLabelAlias();
return switch (alias) {
case "Items" -> EntityType.ITEM;
case "Users" -> EntityType.USER;
default -> throw new IllegalArgumentException(String.format("Entity type %s is not supported", alias));
};
}
}
89 changes: 89 additions & 0 deletions src/main/java/org/folio/bulkops/service/QueryService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.folio.bulkops.service;

import static org.folio.bulkops.domain.dto.OperationStatusType.CANCELLED;
import static org.folio.bulkops.domain.dto.OperationStatusType.FAILED;
import static org.folio.bulkops.domain.dto.OperationStatusType.RETRIEVING_IDENTIFIERS;
import static org.folio.bulkops.domain.dto.OperationStatusType.SAVED_IDENTIFIERS;
import static org.folio.bulkops.util.Constants.NEW_LINE_SEPARATOR;
import static org.folio.spring.scope.FolioExecutionScopeExecutionContextManager.getRunnableWithCurrentFolioContext;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.bulkops.client.QueryClient;
import org.folio.bulkops.client.RemoteFileSystemClient;
import org.folio.bulkops.domain.entity.BulkOperation;
import org.folio.bulkops.repository.BulkOperationRepository;
import org.folio.querytool.domain.dto.SubmitQuery;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Service
@Log4j2
@RequiredArgsConstructor
public class QueryService {
public static final String QUERY_FILENAME_TEMPLATE = "%1$s/Query-%1$s.csv";

private final QueryClient queryClient;
private final BulkOperationRepository bulkOperationRepository;
private final RemoteFileSystemClient remoteFileSystemClient;

private final ExecutorService executor = Executors.newCachedThreadPool();

public UUID executeQuery(SubmitQuery submitQuery) {
return queryClient.executeQuery(submitQuery).getQueryId();
}

public BulkOperation checkQueryExecutionStatus(BulkOperation bulkOperation) {
var queryResult = queryClient.getQuery(bulkOperation.getFqlQueryId());
return switch (queryResult.getStatus()) {
case SUCCESS -> {
if (queryResult.getTotalRecords() == 0) {
yield failBulkOperation(bulkOperation, "No records found for the query");
}
executor.execute(getRunnableWithCurrentFolioContext(() -> saveIdentifiers(bulkOperation)));
bulkOperation.setStatus(RETRIEVING_IDENTIFIERS);
yield bulkOperationRepository.save(bulkOperation);
}
case FAILED -> failBulkOperation(bulkOperation, queryResult.getFailureReason());
case CANCELLED -> cancelBulkOperation((bulkOperation));
case IN_PROGRESS -> bulkOperation;
};
}

private void saveIdentifiers(BulkOperation bulkOperation) {
try {
var identifiersString = queryClient.getSortedIds(bulkOperation.getFqlQueryId(), 0, Integer.MAX_VALUE).stream()
.map(UUID::toString)
.collect(Collectors.joining(NEW_LINE_SEPARATOR));
var path = String.format(QUERY_FILENAME_TEMPLATE, bulkOperation.getId());
remoteFileSystemClient.put(new ByteArrayInputStream(identifiersString.getBytes()), path);
bulkOperation.setLinkToTriggeringCsvFile(path);
bulkOperation.setStatus(SAVED_IDENTIFIERS);
bulkOperationRepository.save(bulkOperation);
} catch (Exception e) {
var errorMessage = "Failed to save identifiers, reason: " + e.getMessage();
log.error(errorMessage);
failBulkOperation(bulkOperation, errorMessage);
}
}

private BulkOperation failBulkOperation(BulkOperation bulkOperation, String errorMessage) {
bulkOperation.setStatus(FAILED);
bulkOperation.setErrorMessage(errorMessage);
bulkOperation.setEndTime(LocalDateTime.now());
return bulkOperationRepository.save(bulkOperation);
}

private BulkOperation cancelBulkOperation(BulkOperation bulkOperation) {
bulkOperation.setStatus(CANCELLED);
bulkOperation.setErrorMessage("Query execution was cancelled");
bulkOperation.setEndTime(LocalDateTime.now());
return bulkOperationRepository.save(bulkOperation);
}
}
1 change: 1 addition & 0 deletions src/main/resources/db/changelog/changelog-master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
<include file="changes/08-11-2023_add_electronic_access_options_types.xml" relativeToChangelogFile="true"/>
<include file="changes/27-12-2023_add_types_for_instance.xml" relativeToChangelogFile="true"/>
<include file="changes/05-01-2024_add_staff_suppress.xml" relativeToChangelogFile="true"/>
<include file="changes/26-01-2024_add_query_fields_and_statuses.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE bulk_operation ADD COLUMN IF NOT EXISTS fql_query_id UUID;
ALTER TABLE bulk_operation ADD COLUMN IF NOT EXISTS fql_query TEXT;

ALTER TYPE OperationStatusType ADD VALUE IF NOT EXISTS 'EXECUTING_QUERY';
ALTER TYPE OperationStatusType ADD VALUE IF NOT EXISTS 'RETRIEVING_IDENTIFIERS';
ALTER TYPE OperationStatusType ADD VALUE IF NOT EXISTS 'SAVED_IDENTIFIERS';
Loading

0 comments on commit 93c07c6

Please sign in to comment.