diff --git a/.gitignore b/.gitignore index 549e00a2..a8c95021 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +.env ### STS ### .apt_generated diff --git a/NEWS.md b/NEWS.md index 3e13bb31..f3145148 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,14 @@ -# 1.0.0 -- Initial release +# 1.0.x + +## 1.0.3 +- [MODFQMMGR-58](https://issues.folio.org/browse/MODFQMMGR-58) Refactor drv_item_callnumber_location view +- [MODFQMMGR-76](https://issues.folio.org/browse/MODFQMMGR-76) Periodically refresh materialized views + +## 1.0.2 +- Remove the instance_title_searchable field from the Items entity type +- Purge old query results based on query start date/time instead of the end date/time +- Fix bug in user preferred contact type +- Update the provided `_tenant` interface in the module descriptor to 2.0 ## 1.0.1 - [MODFQMMGR-57](https://issues.folio.org/browse/MODFQMMGR-57) Use a different version of f_unaccent(), to allow us to make use of an index @@ -8,3 +17,6 @@ - [MODFQMMGR-67](https://issues.folio.org/browse/MODFQMMGR-67) Enable batched inserts - [MODFQMMGR-71](https://issues.folio.org/browse/MODFQMMGR-71) Update item and user entity types - [MODFQMMGR-31](https://issues.folio.org/browse/MODFQMMGR-31) Fix Users dropdown + +## 1.0.0 +- Initial release diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index c6d18b4e..a0e4b8e8 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1,18 +1,21 @@ { "id": "@artifactId@-@version@", - "name": "The module descriptor for mod-fqm-manager.", + "name": "FQM Manager Module", "provides": [ { "id": "_tenant", - "version": "1.2", + "version": "2.0", "interfaceType": "system", "handlers": [ { "methods": ["POST"], - "pathPattern": "/_/tenant" - }, { - "methods": ["DELETE"], - "pathPattern": "/_/tenant" + "pathPattern": "/_/tenant", + "permissionsRequired": [] + }, + { + "methods": ["GET", "DELETE"], + "pathPattern": "/_/tenant/{id}", + "permissionsRequired": [] } ] }, @@ -34,6 +37,11 @@ "methods": ["GET"], "pathPattern": "/entity-types/{entity-type-id}/columns/{column-name}/values", "permissionsRequired": ["fqm.entityTypes.item.columnValues.get"] + }, + { + "methods": ["POST"], + "pathPattern": "/entity-types/materialized-views/refresh", + "permissionsRequired": ["fqm.materializedViews.post"] } ] }, @@ -84,10 +92,22 @@ "interfaceType": "system", "handlers": [ { - "methods": [ "POST" ], + "methods": ["POST"], "pathPattern": "/query/purge", "unit": "hour", - "delay": "1" + "delay": "1", + "modulePermissions": [ + "fqm.query.purge" + ] + }, + { + "methods": ["POST"], + "pathPattern": "/entity-types/materialized-views/refresh", + "unit": "hour", + "delay": "24", + "modulePermissions": [ + "fqm.materializedViews.post" + ] } ] } @@ -141,6 +161,12 @@ "description": "Run a query synchronously and get results", "visible": true }, + { + "permissionName": "fqm.materializedViews.post", + "displayName": "FQM - Refresh materialized views", + "description": "Refresh FQM materialized views", + "visible": true + }, { "permissionName": "fqm.query.all", "displayName": "FQM - All permissions", diff --git a/pom.xml b/pom.xml index dbaa8d85..ea457a3d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.folio mod-fqm-manager mod-fqm-manager - 1.0.2-SNAPSHOT + 1.0.4-SNAPSHOT FOLIO Query Machine manager jar @@ -373,10 +373,10 @@ - https://github.com/EBSCOIS/${project.artifactId} - scm:git:git://github.com/EBSCOIS/${project.artifactId}.git - scm:git:git@github.com:EBSCOIS/${project.artifactId}.git - v1.0.0 + https://github.com/folio-org/${artifactId} + scm:git:git://github.com/folio-org/${artifactId}.git + scm:git:git@github.com:folio-org/${artifactId}.git + HEAD diff --git a/src/main/java/org/folio/fqm/repository/MaterializedViewRefreshRepository.java b/src/main/java/org/folio/fqm/repository/MaterializedViewRefreshRepository.java new file mode 100644 index 00000000..e45d9f83 --- /dev/null +++ b/src/main/java/org/folio/fqm/repository/MaterializedViewRefreshRepository.java @@ -0,0 +1,30 @@ +package org.folio.fqm.repository; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.jooq.DSLContext; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +@Log4j2 +public class MaterializedViewRefreshRepository { + private static final String REFRESH_MATERIALIZED_VIEW_SQL = "REFRESH MATERIALIZED VIEW CONCURRENTLY "; + + private static final List materializedViewNames = List.of( + "drv_circulation_loan_status", + "drv_inventory_item_status" + ); + + private final DSLContext jooqContext; + + public void refreshMaterializedViews(String tenantId) { + for (String matViewName : materializedViewNames) { + String fullName = tenantId + "_mod_fqm_manager." + matViewName; + log.info("Refreshing materialized view {}", fullName); + jooqContext.execute(REFRESH_MATERIALIZED_VIEW_SQL + fullName); + } + } +} diff --git a/src/main/java/org/folio/fqm/repository/QueryRepository.java b/src/main/java/org/folio/fqm/repository/QueryRepository.java index 73c27312..d04a3e13 100644 --- a/src/main/java/org/folio/fqm/repository/QueryRepository.java +++ b/src/main/java/org/folio/fqm/repository/QueryRepository.java @@ -73,10 +73,10 @@ public Optional getQuery(UUID queryId, boolean useCache) { .fetchOneInto(Query.class)); } - public List getQueryIdsCompletedBefore(Duration duration) { + public List getQueryIdsStartedBefore(Duration duration) { return jooqContext.select(field(QUERY_ID)) .from(table(QUERY_DETAILS_TABLE)) - .where(field("end_date"). + .where(field("start_date"). lessOrEqual(OffsetDateTime.now().minus(duration))) .fetchInto(UUID.class); } diff --git a/src/main/java/org/folio/fqm/resource/MaterializedViewRefreshController.java b/src/main/java/org/folio/fqm/resource/MaterializedViewRefreshController.java new file mode 100644 index 00000000..7da2c3cb --- /dev/null +++ b/src/main/java/org/folio/fqm/resource/MaterializedViewRefreshController.java @@ -0,0 +1,21 @@ +package org.folio.fqm.resource; + +import lombok.RequiredArgsConstructor; +import org.folio.fqm.service.MaterializedViewRefreshService; +import org.folio.spring.FolioExecutionContext; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class MaterializedViewRefreshController implements MaterializedViewsApi { + private final FolioExecutionContext executionContext; + private final MaterializedViewRefreshService materializedViewRefreshService; + + @Override + public ResponseEntity refreshMaterializedViews() { + materializedViewRefreshService.refreshMaterializedViews(executionContext.getTenantId()); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/org/folio/fqm/service/MaterializedViewRefreshService.java b/src/main/java/org/folio/fqm/service/MaterializedViewRefreshService.java new file mode 100644 index 00000000..a7d1e00f --- /dev/null +++ b/src/main/java/org/folio/fqm/service/MaterializedViewRefreshService.java @@ -0,0 +1,15 @@ +package org.folio.fqm.service; + +import lombok.RequiredArgsConstructor; +import org.folio.fqm.repository.MaterializedViewRefreshRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MaterializedViewRefreshService { + private final MaterializedViewRefreshRepository materializedViewRefreshRepository; + + public void refreshMaterializedViews(String tenantId) { + materializedViewRefreshRepository.refreshMaterializedViews(tenantId); + } +} diff --git a/src/main/java/org/folio/fqm/service/QueryManagementService.java b/src/main/java/org/folio/fqm/service/QueryManagementService.java index f6d3f4e1..925b65b0 100644 --- a/src/main/java/org/folio/fqm/service/QueryManagementService.java +++ b/src/main/java/org/folio/fqm/service/QueryManagementService.java @@ -133,7 +133,7 @@ public Optional getQuery(UUID queryId, boolean includeResults, int */ @Transactional public PurgedQueries deleteOldQueries() { - List queryIds = queryRepository.getQueryIdsCompletedBefore(queryRetentionDuration); + List queryIds = queryRepository.getQueryIdsStartedBefore(queryRetentionDuration); log.info("Deleting the queries with queryIds {}", queryIds); deleteQueryAndResults(queryIds); return new PurgedQueries().deletedQueryIds(queryIds); diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 731d2d0a..3389bd25 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -6,5 +6,7 @@ + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/changelog-v1.0.2.xml b/src/main/resources/db/changelog/changes/v1.0.2/changelog-v1.0.2.xml new file mode 100644 index 00000000..838e3368 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/changelog-v1.0.2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/fix_user_details_preferred_contact_type.xml b/src/main/resources/db/changelog/changes/v1.0.2/fix_user_details_preferred_contact_type.xml new file mode 100644 index 00000000..3c0edd39 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/fix_user_details_preferred_contact_type.xml @@ -0,0 +1,98 @@ + + + + + + + + + SELECT + userDetails.jsonb -> 'personal' ->> 'firstName' as user_first_name, + userDetails.jsonb -> 'personal' ->> 'lastName' as user_last_name, + userDetails.jsonb ->>'barcode' as user_barcode, + userDetails.jsonb ->>'username' as username, + userDetails.id as id, + userDetails.jsonb ->> 'externalSystemId' as user_external_system_id, + userDetails.jsonb ->> 'active' as user_active, + userDetails.jsonb -> 'personal' ->> 'email' as user_email, + userDetails.jsonb -> 'metadata' ->> 'createdDate' as user_created_date, + userDetails.jsonb -> 'metadata' ->> 'updatedDate' as user_updated_date, + userDetails.jsonb -> 'personal' ->> 'preferredFirstName' as user_preferred_first_name, + userDetails.jsonb -> 'personal' ->> 'middleName' as user_middle_name, + userDetails.jsonb -> 'personal' ->> 'phone' as user_phone, + userDetails.jsonb -> 'personal' ->> 'mobilePhone' as user_mobile_phone, + userDetails.jsonb -> 'personal' ->> 'dateOfBirth' as user_date_of_birth, + userDetails.jsonb ->> 'expirationDate'::text AS user_expiration_date, + userDetails.jsonb ->> 'enrollmentDate'::text AS user_enrollment_date, + patron_id_ref_data.jsonb ->> 'group'::text AS user_patron_group, + patron_id_ref_data.id::text AS user_patron_group_id, + UserDetails.jsonb -> 'personal' ->> 'preferredContactTypeId' as user_preferred_contact_type_id, + CASE UserDetails.jsonb -> 'personal' ->> 'preferredContactTypeId' + WHEN '001' THEN 'Mail (Primary Address)' + WHEN '002' THEN 'Email' + WHEN '003' THEN 'Text Message' + ELSE 'unknown' + END AS user_preferred_contact_type, + concat_ws(', '::text, + NULLIF(( SELECT subquery.addressline1 + FROM ( SELECT add_id.value ->> 'addressLine1'::text AS addressline1, + row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery + WHERE subquery.row_num = 1), ''::text), + NULLIF(( SELECT subquery.addressline2 + FROM ( SELECT add_id.value ->> 'addressLine2'::text AS addressline2, + row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery + WHERE subquery.row_num = 1), ''::text), + NULLIF(( SELECT subquery.city + FROM ( SELECT add_id.value ->> 'city'::text AS city, + row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery + WHERE subquery.row_num = 1), ''::text), + NULLIF(( SELECT subquery.region + FROM ( SELECT add_id.value ->> 'region'::text AS region, + row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery + WHERE subquery.row_num = 1), ''::text), + NULLIF(( SELECT subquery.postalcode + FROM ( SELECT add_id.value ->> 'postalCode'::text AS postalcode, + row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery + WHERE subquery.row_num = 1), ''::text), + NULLIF(( SELECT subquery.countryid + FROM ( SELECT add_id.value ->> 'countryId'::text AS countryid, + row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery + WHERE subquery.row_num = 1), ''::text)) AS user_primary_address, + ( SELECT array_agg(add_id.value ->> 'city'::text) FILTER (WHERE (add_id.value ->> 'city'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS cities, + ( SELECT array_agg(add_id.value ->> 'region'::text) FILTER (WHERE (add_id.value ->> 'region'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS regions, + ( SELECT array_agg(add_id.value ->> 'countryId'::text) FILTER (WHERE (add_id.value ->> 'countryId'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS country_ids, + ( SELECT array_agg(add_id.value ->> 'postalCode'::text) FILTER (WHERE (add_id.value ->> 'postalCode'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS postal_codes, + ( SELECT array_agg(add_id.value ->> 'addressLine1'::text) FILTER (WHERE (add_id.value ->> 'addressLine1'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS address_line1, + ( SELECT array_agg(add_id.value ->> 'addressLine2'::text) FILTER (WHERE (add_id.value ->> 'addressLine2'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS address_line2, + ( SELECT array_agg(add_id.value ->> 'addressTypeId'::text) FILTER (WHERE (add_id.value ->> 'addressTypeId'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS address_ids, + ( SELECT array_agg(a.jsonb ->> 'addressType'::text) FILTER (WHERE (a.jsonb ->> 'addressType'::text) IS NOT NULL) AS array_agg + FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value) + JOIN src_users_addresstype a ON (add_id.value ->> 'addressTypeId'::text) = a.id::text) AS address_type_names, + array_agg(temp_departments.id::text) FILTER (where temp_departments.id is not null) as department_ids, + array_agg(temp_departments.jsonb ->> 'name') FILTER (where temp_departments.jsonb ->> 'name' is not null) as department_names + FROM src_users_users userdetails + LEFT JOIN src_users_groups patron_id_ref_data ON patron_id_ref_data.id = ((userdetails.jsonb ->> 'patronGroup'::text)::uuid) + LEFT JOIN src_users_departments as temp_departments ON userdetails.jsonb -> 'departments' ?? temp_departments.id::text + GROUP BY userdetails.id, userdetails.jsonb, patron_id_ref_data.id, patron_id_ref_data.jsonb + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/remove_items_holdingsrecord_instance_title_searchable.xml b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_holdingsrecord_instance_title_searchable.xml new file mode 100644 index 00000000..61dd64b8 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_holdingsrecord_instance_title_searchable.xml @@ -0,0 +1,48 @@ + + + + + + + + + SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status'; + + + + + SELECT src_inventory_item.id, + "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'createdDate'::text), 600) AS instance_created_date, + hrim.instanceid AS instance_id, + jsonb_path_query_first(instance_details.jsonb, '$."contributors"[*]?(@."primary" == true)."name"'::jsonpath) #>> '{}'::text[] AS instance_primary_contributor, + instance_details.jsonb ->> 'title'::text AS instance_title, + "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'updatedDate'::text), 600) AS instance_updated_date, + src_inventory_item.jsonb ->> 'barcode'::text AS item_barcode, + src_inventory_item.jsonb ->> 'copyNumber'::text AS item_copy_number, + (src_inventory_item.jsonb -> 'metadata'::text) ->> 'createdDate'::text AS item_created_date, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text AS item_effective_call_number_prefix, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text AS item_effective_call_number_callnumber, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text AS item_effective_call_number_suffix, + src_inventory_item.jsonb ->> 'copyNumber'::text AS item_effective_call_number_copynumber, + concat_ws(', '::text, NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text, ''::text), NULLIF(src_inventory_item.jsonb ->> 'copyNumber'::text, ''::text)) AS item_effective_call_number, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text AS item_effective_call_number_typeid, + src_inventory_item.effectivelocationid AS item_effective_location_id, + src_inventory_item.jsonb ->> 'hrid'::text AS item_hrid, + src_inventory_item.holdingsrecordid AS holdings_id, + src_inventory_item.jsonb ->> 'itemLevelCallNumber'::text AS item_level_call_number, + src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text AS item_level_call_number_typeid, + src_inventory_item.materialtypeid AS item_material_type_id, + src_inventory_item.permanentlocationid AS item_permanent_location_id, + src_inventory_item.temporarylocationid AS item_temporary_location_id, + "left"(lower(${tenant_id}_mod_inventory_storage.f_unaccent((src_inventory_item.jsonb -> 'status'::text) ->> 'name'::text)), 600) AS item_status, + (src_inventory_item.jsonb -> 'metadata'::text) ->> 'updatedDate'::text AS item_updated_date + FROM src_inventory_item + JOIN src_inventory_holdings_record hrim ON src_inventory_item.holdingsrecordid = hrim.id + JOIN src_inventory_instance instance_details ON hrim.instanceid = instance_details.id; + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/remove_items_instance_title_searchable.xml b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_instance_title_searchable.xml new file mode 100644 index 00000000..a45b9a51 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_instance_title_searchable.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status'; + + + + + SELECT src_inventory_item.id, + "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'createdDate'::text), 600) AS instance_created_date, + hrim.instanceid AS instance_id, + jsonb_path_query_first(instance_details.jsonb, '$."contributors"[*]?(@."primary" == true)."name"'::jsonpath) #>> '{}'::text[] AS instance_primary_contributor, + instance_details.jsonb ->> 'title'::text AS instance_title, + "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'updatedDate'::text), 600) AS instance_updated_date, + lower(src_inventory_item.jsonb ->> 'barcode'::text) AS item_barcode, + src_inventory_item.jsonb ->> 'copyNumber'::text AS item_copy_number, + (src_inventory_item.jsonb -> 'metadata'::text) ->> 'createdDate'::text AS item_created_date, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text AS item_effective_call_number_prefix, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text AS item_effective_call_number_callnumber, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text AS item_effective_call_number_suffix, + src_inventory_item.jsonb ->> 'copyNumber'::text AS item_effective_call_number_copynumber, + concat_ws(', '::text, NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text, ''::text), NULLIF(src_inventory_item.jsonb ->> 'copyNumber'::text, ''::text)) AS item_effective_call_number, + call_number_type_ref_data.jsonb ->> 'name'::text AS item_effective_call_number_type_name, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text AS item_effective_call_number_typeid, + loclib_ref_data.jsonb ->> 'code'::text AS item_effective_library_code, + loclib_ref_data.id AS item_effective_library_id, + loclib_ref_data.jsonb ->> 'name'::text AS item_effective_library_name, + src_inventory_item.effectivelocationid AS item_effective_location_id, + effective_location_ref_data.jsonb ->> 'name'::text AS item_effective_location_name, + src_inventory_item.jsonb ->> 'hrid'::text AS item_hrid, + src_inventory_item.holdingsrecordid AS holdings_id, + src_inventory_item.jsonb ->> 'itemLevelCallNumber'::text AS item_level_call_number, + call_item_number_type_ref_data.jsonb ->> 'name'::text AS item_level_call_number_type_name, + src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text AS item_level_call_number_typeid, + material_type_ref_data.jsonb ->> 'name'::text AS item_material_type, + src_inventory_item.materialtypeid AS item_material_type_id, + src_inventory_item.permanentlocationid AS item_permanent_location_id, + permanent_location_ref_data.jsonb ->> 'name'::text AS item_permanent_location_name, + src_inventory_item.temporarylocationid AS item_temporary_location_id, + temporary_location_ref_data.jsonb ->> 'name'::text AS item_temporary_location_name, + "left"(lower(${tenant_id}_mod_inventory_storage.f_unaccent((src_inventory_item.jsonb -> 'status'::text) ->> 'name'::text)), 600) AS item_status, + (src_inventory_item.jsonb -> 'metadata'::text) ->> 'updatedDate'::text AS item_updated_date + FROM src_inventory_item + LEFT JOIN src_inventory_location effective_location_ref_data ON effective_location_ref_data.id = src_inventory_item.effectivelocationid + LEFT JOIN src_inventory_call_number_type call_number_type_ref_data ON call_number_type_ref_data.id::text = ((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text) + LEFT JOIN src_inventory_call_number_type call_item_number_type_ref_data ON call_item_number_type_ref_data.id::text = (src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text) + LEFT JOIN src_inventory_loclibrary loclib_ref_data ON loclib_ref_data.id = effective_location_ref_data.libraryid + LEFT JOIN src_inventory_location permanent_location_ref_data ON permanent_location_ref_data.id = src_inventory_item.permanentlocationid + LEFT JOIN src_inventory_material_type material_type_ref_data ON material_type_ref_data.id = src_inventory_item.materialtypeid + LEFT JOIN src_inventory_location temporary_location_ref_data ON temporary_location_ref_data.id = src_inventory_item.temporarylocationid + JOIN src_inventory_holdings_record hrim ON src_inventory_item.holdingsrecordid = hrim.id + JOIN src_inventory_instance instance_details ON hrim.instanceid = instance_details.id; + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/update_item_details_entity_type_definition.xml b/src/main/resources/db/changelog/changes/v1.0.2/update_item_details_entity_type_definition.xml new file mode 100644 index 00000000..40a939f7 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/update_item_details_entity_type_definition.xml @@ -0,0 +1,322 @@ + + + + + { + "id": "0cb79a4c-f7eb-4941-a104-745224ae0292", + "name": "drv_item_details", + "labelAlias": "Items", + "subEntityTypeIds": [ + "097a6f96-edd0-11ed-a05b-0242ac120003", + "0cb79a4c-f7eb-4941-a104-745224ae0293" + ], + "private": false, + "columns": [ + { + "name": "holdings_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Holdings ID", + "visibleByDefault": false + }, + { + "name": "instance_created_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Instance created date", + "visibleByDefault": false + }, + { + "name": "instance_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Instance ID", + "visibleByDefault": false + }, + { + "name": "instance_primary_contributor", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Instance primary contributor", + "visibleByDefault": false + }, + { + "name": "instance_title", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Instance title", + "visibleByDefault": true + }, + { + "name": "instance_updated_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Instance updated date", + "visibleByDefault": false + }, + { + "name": "item_barcode", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item barcode", + "visibleByDefault": true + }, + { + "name": "item_level_call_number", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item call number", + "visibleByDefault": false + }, + { + "name": "item_level_call_number_type_name", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item call number type name", + "visibleByDefault": false, + "idColumnName": "item_level_call_number_typeid", + "source": { + "entityTypeId": "5c8315be-13f5-4df5-ae8b-086bae83484d", + "columnName": "call_number_type_name" + } + }, + { + "name": "item_level_call_number_typeid", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item call number type ID", + "visibleByDefault": false + }, + { + "name": "item_copy_number", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item copy number", + "visibleByDefault": true + }, + { + "name": "item_created_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Item created date", + "visibleByDefault": false + }, + { + "name": "item_effective_call_number", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item effective call number", + "visibleByDefault": true + }, + { + "name": "item_effective_call_number_type_name", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item effective call number type name", + "visibleByDefault": false, + "idColumnName": "item_effective_call_number_typeid", + "source": { + "entityTypeId": "5c8315be-13f5-4df5-ae8b-086bae83484d", + "columnName": "call_number_type_name" + } + }, + { + "name": "item_effective_call_number_typeid", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item effective call number type ID", + "visibleByDefault": false + }, + { + "name": "item_effective_library_code", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item effective library code", + "visibleByDefault": false + }, + { + "name": "item_effective_library_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item effective library ID", + "visibleByDefault": false + }, + { + "name": "item_effective_library_name", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item effective library name", + "visibleByDefault": false, + "idColumnName": "item_effective_library_id", + "source": { + "entityTypeId": "cf9f5c11-e943-483c-913b-81d1e338accc", + "columnName": "loclibrary_name" + } + }, + { + "name": "item_effective_location_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item effective location ID", + "visibleByDefault": false + }, + { + "name": "item_effective_location_name", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item effective location name", + "visibleByDefault": true, + "idColumnName": "item_effective_location_id", + "source": { + "entityTypeId": "a9d6305e-fdb4-4fc4-8a73-4a5f76d8410b", + "columnName": "location_name" + } + }, + { + "name": "item_hrid", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item hrid", + "visibleByDefault": false + }, + { + "name": "id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item ID", + "visibleByDefault": false + }, + { + "name": "item_material_type", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item material type", + "visibleByDefault": false, + "idColumnName": "item_material_type_id", + "source": { + "entityTypeId": "917ea5c8-cafe-4fa6-a942-e2388a88c6f6", + "columnName": "material_type_name" + } + }, + { + "name": "item_material_type_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item material ID", + "visibleByDefault": false + }, + { + "name": "item_permanent_location_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item permanent ID", + "visibleByDefault": false + }, + { + "name": "item_permanent_location_name", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item permanent location name", + "visibleByDefault": false, + "idColumnName": "item_permanent_location_id", + "source": { + "entityTypeId": "a9d6305e-fdb4-4fc4-8a73-4a5f76d8410b", + "columnName": "location_name" + } + }, + { + "name": "item_status", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item status", + "visibleByDefault": false, + "source": { + "entityTypeId": "a1a37288-1afe-4fa5-ab59-a5bcf5d8ca2d", + "columnName": "item_status" + } + }, + { + "name": "item_temporary_location_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item temporary ID", + "visibleByDefault": false + }, + { + "name": "item_temporary_location_name", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item temporary location name", + "visibleByDefault": false, + "idColumnName": "item_temporary_location_id", + "source": { + "entityTypeId": "a9d6305e-fdb4-4fc4-8a73-4a5f76d8410b", + "columnName": "location_name" + } + }, + { + "name": "item_updated_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Item updated date", + "visibleByDefault": false + } + ], + "defaultSort": [ + { + "columnName": "item_effective_location_name", + "direction": "ASC" + }, + { + "columnName": "instance_title", + "direction": "ASC" + }, + { + "columnName": "instance_primary_contributor", + "direction": "ASC" + }, + { + "columnName": "id", + "direction": "ASC" + } + ] + } + + id = '0cb79a4c-f7eb-4941-a104-745224ae0292' + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/update_item_holdingsrecord_entity_type_definition.xml b/src/main/resources/db/changelog/changes/v1.0.2/update_item_holdingsrecord_entity_type_definition.xml new file mode 100644 index 00000000..958e1928 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/update_item_holdingsrecord_entity_type_definition.xml @@ -0,0 +1,193 @@ + + + + + { + "id": "0cb79a4c-f7eb-4941-a104-745224ae0293", + "name": "drv_item_holdingsrecord_instance", + "labelAlias": "Items, Holdings records, Instance", + "private": true, + "columns": [ + { + "name": "holdings_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Holdings ID", + "visibleByDefault": false + }, + { + "name": "instance_created_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Instance created date", + "visibleByDefault": false + }, + { + "name": "instance_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Instance ID", + "visibleByDefault": false + }, + { + "name": "instance_primary_contributor", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Instance primary contributor", + "visibleByDefault": false + }, + { + "name": "instance_title", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Instance title", + "visibleByDefault": true + }, + { + "name": "instance_updated_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Instance updated date", + "visibleByDefault": false + }, + { + "name": "item_barcode", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item barcode", + "visibleByDefault": true + }, + { + "name": "item_level_call_number", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item call number", + "visibleByDefault": false + }, + { + "name": "item_level_call_number_typeid", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item call number type ID", + "visibleByDefault": false + }, + { + "name": "item_copy_number", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item copy number", + "visibleByDefault": true + }, + { + "name": "item_created_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Item created date", + "visibleByDefault": false + }, + { + "name": "item_effective_call_number", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item effective call number", + "visibleByDefault": true + }, + { + "name": "item_effective_call_number_typeid", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item effective call number type ID", + "visibleByDefault": false + }, + { + "name": "item_effective_location_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item location ID", + "visibleByDefault": false + }, + { + "name": "item_hrid", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item hrid", + "visibleByDefault": false + }, + { + "name": "id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item ID", + "visibleByDefault": false + }, + { + "name": "item_material_type_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item material ID", + "visibleByDefault": false + }, + { + "name": "item_permanent_location_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item permanent ID", + "visibleByDefault": false + }, + { + "name": "item_status", + "dataType": { + "dataType": "stringType" + }, + "labelAlias": "Item status", + "visibleByDefault": false, + "source": { + "entityTypeId": "a1a37288-1afe-4fa5-ab59-a5bcf5d8ca2d", + "columnName": "item_status" + } + }, + { + "name": "item_temporary_location_id", + "dataType": { + "dataType": "rangedUUIDType" + }, + "labelAlias": "Item temporary ID", + "visibleByDefault": false + }, + { + "name": "item_updated_date", + "dataType": { + "dataType": "dateType" + }, + "labelAlias": "Item updated date", + "visibleByDefault": false + } + ] + } + + id = '0cb79a4c-f7eb-4941-a104-745224ae0293' + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.2/update_user_details_entity_type_definition.xml b/src/main/resources/db/changelog/changes/v1.0.2/update_user_details_entity_type_definition.xml new file mode 100644 index 00000000..1b26988d --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.2/update_user_details_entity_type_definition.xml @@ -0,0 +1,224 @@ + + + + + { + "id": "0069cf6f-2833-46db-8a51-8934769b8289", + "name":"drv_user_details", + "labelAlias" : "Users", + "private" : false, + "columns": [ + { + "name": "user_active", + "dataType":{ + "dataType":"booleanType" + }, + "labelAlias": "User active", + "visibleByDefault": true, + "values": [ + { + "value": "true", + "label": "True" + }, + { + "value": "false", + "label": "False" + } + ] + }, + { + "name": "user_barcode", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User barcode", + "visibleByDefault": true + }, + { + "name": "user_created_date", + "dataType":{ + "dataType":"dateType" + }, + "labelAlias": "User created date", + "visibleByDefault": false + }, + { + "name": "user_date_of_birth", + "dataType":{ + "dataType":"dateType" + }, + "labelAlias": "User date of birth", + "visibleByDefault": false + }, + { + "name": "user_email", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User email", + "visibleByDefault": false + }, + { + "name": "user_enrollment_date", + "dataType":{ + "dataType":"dateType" + }, + "labelAlias": "User enrollment date", + "visibleByDefault": false + }, + { + "name": "user_expiration_date", + "dataType":{ + "dataType":"dateType" + }, + "labelAlias": "User expiration date", + "visibleByDefault": false + }, + { + "name": "user_external_system_id", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User external system ID", + "visibleByDefault": false + }, + { + "name": "user_first_name", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User first name", + "visibleByDefault": true + }, + { + "name": "id", + "dataType":{ + "dataType":"rangedUUIDType" + }, + "labelAlias": "User ID", + "visibleByDefault": true + }, + { + "name": "user_last_name", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User last name", + "visibleByDefault": true + }, + { + "name": "user_middle_name", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User middle name", + "visibleByDefault": false + }, + { + "name": "user_mobile_phone", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User mobile phone", + "visibleByDefault": false + }, + { + "name": "user_patron_group", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User patron group", + "visibleByDefault": false, + "idColumnName": "user_patron_group_id", + "source": { + "entityTypeId": "e611264d-377e-4d87-a93f-f1ca327d3db0", + "columnName": "group" + } + }, + { + "name": "user_patron_group_id", + "dataType":{ + "dataType":"rangedUUIDType" + }, + "labelAlias": "User patron group ID", + "visibleByDefault": false + }, + { + "name": "user_phone", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User phone", + "visibleByDefault": true + }, + { + "name": "user_preferred_contact_type", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User preferred contact type", + "visibleByDefault": false, + "values": [ + { + "value": "Email", + "label": "Email" + }, + { + "value": "Mail (Primary Address)", + "label": "Mail (Primary Address)" + }, + { + "value": "Text Message", + "label": "Text Message" + } + ] + }, + { + "name": "user_preferred_first_name", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User preferred first name", + "visibleByDefault": true + }, + { + "name": "user_primary_address", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "User primary address", + "visibleByDefault": false + }, + { + "name": "user_updated_date", + "dataType":{ + "dataType":"dateType" + }, + "labelAlias": "User updated date", + "visibleByDefault": false + }, + { + "name": "username", + "dataType":{ + "dataType":"stringType" + }, + "labelAlias": "Username", + "visibleByDefault": true + } + ], + "defaultSort": [ + { + "columnName": "id", + "direction": "ASC" + } + ] + } + + id = '0069cf6f-2833-46db-8a51-8934769b8289' + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.3/add_materialized_view_indexes.xml b/src/main/resources/db/changelog/changes/v1.0.3/add_materialized_view_indexes.xml new file mode 100644 index 00000000..d9b5eaec --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.3/add_materialized_view_indexes.xml @@ -0,0 +1,22 @@ + + + + + + SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status'; + + + SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_circulation_loan_status'; + + + + + + + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.3/changelog-v1.0.3.xml b/src/main/resources/db/changelog/changes/v1.0.3/changelog-v1.0.3.xml new file mode 100644 index 00000000..52ca9bd9 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.3/changelog-v1.0.3.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/main/resources/db/changelog/changes/v1.0.3/refactor_drv_item_callnumber_location.xml b/src/main/resources/db/changelog/changes/v1.0.3/refactor_drv_item_callnumber_location.xml new file mode 100644 index 00000000..ad326b43 --- /dev/null +++ b/src/main/resources/db/changelog/changes/v1.0.3/refactor_drv_item_callnumber_location.xml @@ -0,0 +1,53 @@ + + + + + + + + + + SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status'; + + + + + SELECT + src_inventory_item.id, + src_inventory_item.holdingsrecordid AS item_holdings_record_id, + src_inventory_item.jsonb ->> 'hrid'::text AS item_hrid, + src_inventory_item.jsonb ->> 'itemLevelCallNumber'::text AS item_level_call_number, + src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text AS item_level_call_number_typeid, + call_item_number_type_ref_data.jsonb ->> 'name'::text AS item_level_call_number_type_name, + concat_ws(', '::text, NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text, ''::text), NULLIF(src_inventory_item.jsonb ->> 'copyNumber'::text, ''::text)) AS item_effective_call_number, + (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text AS item_effective_call_number_typeid, + call_number_type_ref_data.jsonb ->> 'name'::text AS item_effective_call_number_type_name, + loclib_ref_data.id AS item_effective_library_id, + loclib_ref_data.jsonb ->> 'name'::text AS item_effective_library_name, + loclib_ref_data.jsonb ->> 'code'::text AS item_effective_library_code, + "left"(lower(${tenant_id}_mod_inventory_storage.f_unaccent((src_inventory_item.jsonb -> 'status'::text) ->> 'name'::text)), 600) AS item_status, + src_inventory_item.jsonb ->> 'copyNumber'::text AS item_copy_number, + src_inventory_item.jsonb ->> 'barcode'::text AS item_barcode, + (src_inventory_item.jsonb -> 'metadata'::text) ->> 'createdDate'::text AS item_created_date, + (src_inventory_item.jsonb -> 'metadata'::text) ->> 'updatedDate'::text AS item_updated_date, + effective_location_ref_data.id AS item_effective_location_id, + effective_location_ref_data.jsonb ->> 'name'::text AS item_effective_location_name, + permanent_location_ref_data.id AS item_permanent_location_id, + permanent_location_ref_data.jsonb ->> 'name'::text AS item_permanent_location_name, + material_type_ref_data.id AS item_material_type_id, + material_type_ref_data.jsonb ->> 'name'::text AS item_material_type + FROM src_inventory_item + LEFT JOIN src_inventory_location effective_location_ref_data ON effective_location_ref_data.id = src_inventory_item.effectivelocationid + LEFT JOIN src_inventory_call_number_type call_number_type_ref_data ON call_number_type_ref_data.id::text = ((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text) + LEFT JOIN src_inventory_call_number_type call_item_number_type_ref_data ON call_item_number_type_ref_data.id::text = (src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text) + LEFT JOIN src_inventory_loclibrary loclib_ref_data ON loclib_ref_data.id = effective_location_ref_data.libraryid + LEFT JOIN src_inventory_location permanent_location_ref_data ON permanent_location_ref_data.id = src_inventory_item.permanentlocationid + LEFT JOIN src_inventory_material_type material_type_ref_data ON material_type_ref_data.id = src_inventory_item.materialtypeid; + + + + diff --git a/src/main/resources/swagger.api/mod-fqm-manager.yaml b/src/main/resources/swagger.api/mod-fqm-manager.yaml index 789f6019..c38a0739 100644 --- a/src/main/resources/swagger.api/mod-fqm-manager.yaml +++ b/src/main/resources/swagger.api/mod-fqm-manager.yaml @@ -44,6 +44,19 @@ paths: $ref: '#/components/responses/badRequestResponse' '500': $ref: '#/components/responses/internalServerErrorResponse' + /entity-types/materialized-views/refresh: + post: + operationId: refreshMaterializedViews + tags: + - materializedViews + description: Refresh all materialized views for a tenant. + responses: + '204': + description: 'Views refreshed' + '400': + $ref: '#/components/responses/badRequestResponse' + '500': + $ref: '#/components/responses/internalServerErrorResponse' components: diff --git a/src/test/java/org/folio/fqm/controller/MaterializedViewRefreshControllerTest.java b/src/test/java/org/folio/fqm/controller/MaterializedViewRefreshControllerTest.java new file mode 100644 index 00000000..27acab39 --- /dev/null +++ b/src/test/java/org/folio/fqm/controller/MaterializedViewRefreshControllerTest.java @@ -0,0 +1,43 @@ +package org.folio.fqm.controller; + +import org.folio.fqm.resource.MaterializedViewRefreshController; +import org.folio.fqm.service.MaterializedViewRefreshService; +import org.folio.spring.FolioExecutionContext; +import org.folio.spring.integration.XOkapiHeaders; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(MaterializedViewRefreshController.class) +class MaterializedViewRefreshControllerTest { + @Autowired + private MockMvc mockMvc; + @MockBean + private FolioExecutionContext executionContext; + @MockBean + private MaterializedViewRefreshService materializedViewRefreshService; + + @Test + void refreshMaterializedViewsTest() throws Exception { + String tenantId = "tenant_01"; + RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/entity-types/materialized-views/refresh") + .header(XOkapiHeaders.TENANT, tenantId) + .contentType(APPLICATION_JSON); + when(executionContext.getTenantId()).thenReturn(tenantId); + doNothing().when(materializedViewRefreshService).refreshMaterializedViews(tenantId); + mockMvc.perform(requestBuilder) + .andExpect(status().isNoContent()); + verify(materializedViewRefreshService, times(1)).refreshMaterializedViews(tenantId); + } +} diff --git a/src/test/java/org/folio/fqm/repository/MaterializedViewRefreshRepositoryTest.java b/src/test/java/org/folio/fqm/repository/MaterializedViewRefreshRepositoryTest.java new file mode 100644 index 00000000..565b963c --- /dev/null +++ b/src/test/java/org/folio/fqm/repository/MaterializedViewRefreshRepositoryTest.java @@ -0,0 +1,29 @@ +package org.folio.fqm.repository; + +import org.jooq.DSLContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class MaterializedViewRefreshRepositoryTest { + @InjectMocks + private MaterializedViewRefreshRepository materializedViewRefreshRepository; + @Mock + private DSLContext jooqContext; + + @Test + void refreshMaterializedViewsTest() { + String tenantId = "tenant_01"; + String expectedItemStatusSql = "REFRESH MATERIALIZED VIEW CONCURRENTLY tenant_01_mod_fqm_manager.drv_inventory_item_status"; + String expectedLoanStatusSql = "REFRESH MATERIALIZED VIEW CONCURRENTLY tenant_01_mod_fqm_manager.drv_circulation_loan_status"; + when(jooqContext.execute(anyString())).thenReturn(1); + materializedViewRefreshRepository.refreshMaterializedViews(tenantId); + verify(jooqContext, times(1)).execute(expectedItemStatusSql); + verify(jooqContext, times(1)).execute(expectedLoanStatusSql); + } +} diff --git a/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java b/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java index bce45bdf..f1b60bfc 100644 --- a/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java +++ b/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java @@ -12,9 +12,9 @@ import java.time.Duration; import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; -import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.util.StringUtils.hasText; @@ -86,15 +86,26 @@ void shouldGetQueriesForDeletion() throws InterruptedException { {"field1": {"$in": ["value1", "value2", "value3", "value4", "value5" ] }} """; List fields = List.of("id", "field1"); - Query query = new Query(queryId, UUID.randomUUID(), fqlQuery, fields, + Query queryToDelete = new Query(queryId, UUID.randomUUID(), fqlQuery, fields, UUID.randomUUID(), OffsetDateTime.now(), null, QueryStatus.IN_PROGRESS, null); - repo.saveQuery(query); + repo.saveQuery(queryToDelete); Query updatedQuery = new Query(queryId, UUID.randomUUID(), fqlQuery, fields, UUID.randomUUID(), null, OffsetDateTime.now(), QueryStatus.SUCCESS, null); + + UUID queryId2 = UUID.randomUUID(); + Query queryToNotDelete = new Query(queryId2, UUID.randomUUID(), fqlQuery, fields, + UUID.randomUUID(), OffsetDateTime.now().plusHours(1), null, QueryStatus.IN_PROGRESS, null); + repo.saveQuery(queryToNotDelete); + repo.updateQuery(updatedQuery.queryId(), updatedQuery.status(), updatedQuery.endDate(), updatedQuery.failureReason()); - List expectedIds = List.of(queryId); - List actualIds = repo.getQueryIdsCompletedBefore(Duration.ofMillis(0)); - assertEquals(expectedIds, actualIds); + UUID expectedId = queryId; + List actualIds = repo.getQueryIdsStartedBefore(Duration.ofMillis(0)); + + assertTrue(actualIds.contains(expectedId)); + assertFalse(actualIds.contains(queryId2)); + + // Clean up + repo.deleteQueries(List.of(queryId2)); } @Test diff --git a/src/test/java/org/folio/fqm/service/MaterializedViewRefreshServiceTest.java b/src/test/java/org/folio/fqm/service/MaterializedViewRefreshServiceTest.java new file mode 100644 index 00000000..70afa579 --- /dev/null +++ b/src/test/java/org/folio/fqm/service/MaterializedViewRefreshServiceTest.java @@ -0,0 +1,28 @@ +package org.folio.fqm.service; + +import org.folio.fqm.repository.MaterializedViewRefreshRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class MaterializedViewRefreshServiceTest { + @InjectMocks + private MaterializedViewRefreshService materializedViewRefreshService; + @Mock + private MaterializedViewRefreshRepository materializedViewRefreshRepository; + + @Test + void refreshMaterializedViewsTest() { + String tenantId = "tenant_01"; + doNothing().when(materializedViewRefreshRepository).refreshMaterializedViews(tenantId); + materializedViewRefreshService.refreshMaterializedViews(tenantId); + verify(materializedViewRefreshRepository, times(1)).refreshMaterializedViews(tenantId); + } +} diff --git a/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java b/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java index 36b3d4bd..3885c91d 100644 --- a/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java +++ b/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java @@ -381,7 +381,7 @@ void validateQueryShouldThrowErrorForInvalidFql() { void shouldPurgeQueries() { List queryIds = List.of(UUID.randomUUID(), UUID.randomUUID()); PurgedQueries expectedPurgedQueries = new PurgedQueries().deletedQueryIds(queryIds); - when(queryRepository.getQueryIdsCompletedBefore(Mockito.any())).thenReturn(queryIds); + when(queryRepository.getQueryIdsStartedBefore(Mockito.any())).thenReturn(queryIds); PurgedQueries actualPurgedQueries = queryManagementService.deleteOldQueries(); verify(queryResultsRepository, times(1)).deleteQueryResults(queryIds); verify(queryRepository, times(1)).deleteQueries(queryIds);