Skip to content

Commit

Permalink
MSEARCH-681: Return Unified List of Inventory Locations in a Consorti…
Browse files Browse the repository at this point in the history
…um (#568)

* feat(search-locations): search  locations in consortium

return unified List of Inventory Locations in a Consortium

Closes: MSEARCH-681

---------

Co-authored-by: viacheslav_kolesnyk <[email protected]>
  • Loading branch information
TsaghikKhachatryan and viacheslavkol authored Apr 23, 2024
1 parent af5a303 commit 7c0eab5
Show file tree
Hide file tree
Showing 20 changed files with 700 additions and 7 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Provides `indices v0.7`
* Provides `search v1.3`
* Requires `locations v3.0`
* Provides `consortium-search v1.1`

### Features
* Create location index and process location events ([MSEARCH-703](https://issues.folio.org/browse/MSEARCH-703))
Expand All @@ -14,6 +15,7 @@
* Instance search: add search option that search instances by normalized classification number ([MSEARCH-697](https://issues.folio.org/browse/MSEARCH-697))
* Instance search: make "all" search field option to search by full-text fields ([MSEARCH-606](https://issues.folio.org/browse/MSEARCH-606))
* Facets: add support for instance classification facets ([MSEARCH-606](https://issues.folio.org/browse/MSEARCH-606))
* Return Unified List of Inventory Locations in a Consortium ([MSEARCH-681](https://folio-org.atlassian.net/browse/MSEARCH-681))

### Bug fixes
* Do not delete kafka topics if collection topic is enabled ([MSEARCH-725](https://folio-org.atlassian.net/browse/MSEARCH-725))
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -862,10 +862,11 @@ can't receive ids by query.
### Consortium Search API
Special API that provide consolidated access to records in consortium environment. Works only for central tenant.

| METHOD | URL | DESCRIPTION |
|:-------|:------------------------------|:------------------------------|
| GET | `/search/consortium/holdings` | Returns consolidated holdings |
| GET | `/search/consortium/items` | Returns consolidated items |
| METHOD | URL | DESCRIPTION |
|:-------|:-------------------------------|:-------------------------------|
| GET | `/search/consortium/holdings` | Returns consolidated holdings |
| GET | `/search/consortium/items` | Returns consolidated items |
| GET | `/search/consortium/locations` | Returns consolidated locations |

## Additional Information

Expand Down
19 changes: 18 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
},
{
"id": "consortium-search",
"version": "1.0",
"version": "1.1",
"handlers": [
{
"methods": [
Expand All @@ -167,6 +167,18 @@
"modulePermissions": [
"user-tenants.collection.get"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/search/consortium/locations",
"permissionsRequired": [
"consortium-search.locations.collection.get"
],
"modulePermissions": [
"user-tenants.collection.get"
]
}
]
},
Expand Down Expand Up @@ -635,6 +647,11 @@
"permissionName": "consortium-search.items.collection.get",
"displayName": "Consortium Search - fetch items records",
"description": "Returns items records in consortium"
},
{
"permissionName": "consortium-search.locations.collection.get",
"displayName": "Consortium Search - fetch locations records",
"description": "Returns location records in consortium"
}
],
"launchDescriptor": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import lombok.RequiredArgsConstructor;
import org.folio.search.domain.dto.ConsortiumHoldingCollection;
import org.folio.search.domain.dto.ConsortiumItemCollection;
import org.folio.search.domain.dto.ConsortiumLocationCollection;
import org.folio.search.domain.dto.SortOrder;
import org.folio.search.exception.RequestValidationException;
import org.folio.search.model.service.ConsortiumSearchContext;
import org.folio.search.model.types.ResourceType;
import org.folio.search.rest.resource.SearchConsortiumApi;
import org.folio.search.service.consortium.ConsortiumInstanceService;
import org.folio.search.service.consortium.ConsortiumLocationService;
import org.folio.search.service.consortium.ConsortiumTenantService;
import org.folio.spring.integration.XOkapiHeaders;
import org.springframework.http.ResponseEntity;
Expand All @@ -27,6 +29,7 @@ public class SearchConsortiumController implements SearchConsortiumApi {

private final ConsortiumTenantService consortiumTenantService;
private final ConsortiumInstanceService instanceService;
private final ConsortiumLocationService locationService;

@Override
public ResponseEntity<ConsortiumHoldingCollection> getConsortiumHoldings(String tenantHeader, String instanceId,
Expand Down Expand Up @@ -63,6 +66,22 @@ public ResponseEntity<ConsortiumItemCollection> getConsortiumItems(String tenant
return ResponseEntity.ok(instanceService.fetchItems(context));
}

@Override
public ResponseEntity<ConsortiumLocationCollection> getConsortiumLocations(String tenantHeader,
String tenantId,
Integer limit,
Integer offset,
String sortBy,
SortOrder sortOrder) {
checkAllowance(tenantHeader);
var result = locationService.fetchLocations(tenantHeader, tenantId, limit, offset, sortBy, sortOrder);

return ResponseEntity.ok(new
ConsortiumLocationCollection()
.locations(result.getRecords())
.totalRecords(result.getTotalRecords()));
}

private void checkAllowance(String tenantHeader) {
var centralTenant = consortiumTenantService.getCentralTenant(tenantHeader);
if (centralTenant.isEmpty() || !centralTenant.get().equals(tenantHeader)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.folio.search.repository;

import static org.folio.search.utils.SearchUtils.TENANT_ID_FIELD_NAME;
import static org.folio.search.utils.SearchUtils.performExceptionalOperation;
import static org.opensearch.search.sort.SortOrder.ASC;
import static org.opensearch.search.sort.SortOrder.DESC;

import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.search.domain.dto.ConsortiumLocation;
import org.folio.search.domain.dto.SortOrder;
import org.folio.search.model.SearchResult;
import org.folio.search.service.converter.ElasticsearchDocumentConverter;
import org.jetbrains.annotations.NotNull;
import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.client.RequestOptions;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.sort.SortBuilders;
import org.springframework.stereotype.Repository;


@Log4j2
@Repository
@RequiredArgsConstructor
public class ConsortiumLocationRepository {

public static final String LOCATION_INDEX = "location";
private static final String OPERATION_TYPE = "searchApi";
private final IndexNameProvider indexNameProvider;
private final ElasticsearchDocumentConverter documentConverter;

private final RestHighLevelClient client;

public SearchResult<ConsortiumLocation> fetchLocations(String tenantHeader,
String tenantId,
Integer limit,
Integer offset,
String sortBy,
SortOrder sortOrder) {

var sourceBuilder = getSearchSourceBuilder(tenantId, limit, offset, sortBy, sortOrder);
var response = search(sourceBuilder, tenantHeader);
return documentConverter.convertToSearchResult(response, ConsortiumLocation.class);
}

@NotNull
private static SearchSourceBuilder getSearchSourceBuilder(String tenantId,
Integer limit,
Integer offset,
String sortBy,
SortOrder sortOrder) {
var sourceBuilder = new SearchSourceBuilder();
Optional.ofNullable(tenantId)
.ifPresent(id -> sourceBuilder
.query(QueryBuilders
.termQuery(TENANT_ID_FIELD_NAME, id)));

return sourceBuilder
.from(offset)
.sort(SortBuilders
.fieldSort(sortBy)
.order(sortOrder == SortOrder.DESC ? DESC : ASC))
.size(limit);
}

private SearchResponse search(SearchSourceBuilder sourceBuilder, String tenantHeader) {
var index = indexNameProvider.getIndexName(LOCATION_INDEX, tenantHeader);
var searchRequest = new SearchRequest(index);
searchRequest.source(sourceBuilder);
return performExceptionalOperation(() -> client.search(searchRequest,
RequestOptions.DEFAULT), index, OPERATION_TYPE);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.folio.search.service.consortium;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.search.domain.dto.ConsortiumLocation;
import org.folio.search.domain.dto.SortOrder;
import org.folio.search.model.SearchResult;
import org.folio.search.repository.ConsortiumLocationRepository;
import org.springframework.stereotype.Service;

@Log4j2
@Service
@RequiredArgsConstructor
public class ConsortiumLocationService {

public static final String NAME = "name";
public static final String ID = "id";
public static final String TENANT_ID = "tenantId";
private final ConsortiumLocationRepository repository;

public SearchResult<ConsortiumLocation> fetchLocations(String tenantHeader,
String tenantId,
Integer limit,
Integer offset,
String sortBy,
SortOrder sortOrder) {
log.info("fetching consortium locations for tenant: {}, tenantId: {}, sortBy: {}",
tenantHeader,
tenantId,
sortBy);
validateSortByValue(sortBy);
return repository.fetchLocations(tenantHeader, tenantId, limit, offset, sortBy, sortOrder);
}

private void validateSortByValue(String sortBy) {
if (!(NAME.equals(sortBy) || ID.equals(sortBy) || TENANT_ID.equals(sortBy))) {
throw new IllegalArgumentException("Invalid sortBy value: " + sortBy);
}
}


}
3 changes: 3 additions & 0 deletions src/main/resources/swagger.api/mod-search.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ paths:
/search/consortium/items:
$ref: 'paths/search-consortium/search-consortium-items.yaml'

/search/consortium/locations:
$ref: 'paths/search-consortium/search-consortium-locations.yaml'

/search/index/indices:
$ref: 'paths/search-index/search-index-indices.yaml'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
in: query
name: limit
description: Limit the number of elements returned in the response.
schema:
type: integer
minimum: 0
maximum: 1000
default: 1000
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
in: query
name: sortBy
description: |
Defines a field to sort by.
Possible values:
- id
- tenantId
- name
required: false
schema:
type: string
default: name
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
get:
operationId: getConsortiumLocations
summary: Get Consortium Locations
description: Get a list of locations (only for consortium environment)
tags:
- search-consortium
parameters:
- $ref: '../../parameters/tenant-id-query-param.yaml'
- $ref: '../../parameters/consortium-locations-limit-param.yaml'
- $ref: '../../parameters/offset-param.yaml'
- $ref: '../../parameters/sort-by-location-name-param.yaml'
- $ref: '../../parameters/sort-order-param.yaml'
- $ref: '../../parameters/x-okapi-tenant-header.yaml'
responses:
'200':
description: List of locations
content:
application/json:
schema:
$ref: '../../schemas/entity/consortiumLocationCollection.yaml'
'400':
$ref: '../../responses/badRequestResponse.yaml'
'500':
$ref: '../../responses/internalServerErrorResponse.yaml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type: object
properties:
id:
description: Location ID
type: string
name:
description: Location name
type: string
tenantId:
description: Tenant ID of the Location
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type: object
properties:
locations:
type: array
items:
$ref: './consortiumLocation.yaml'
totalRecords:
type: integer
Loading

0 comments on commit 7c0eab5

Please sign in to comment.