From 61f5bff663efdf83b6bb5183d80d3c37e64f181b Mon Sep 17 00:00:00 2001 From: EthanFreestone <54310740+EthanFreestone@users.noreply.github.com> Date: Fri, 17 May 2024 16:18:08 +0100 Subject: [PATCH 1/5] ERM-3187: Re-write query to show "list of resources in an agreement" for improved performance (#767) * feat: Direct resources on an agreement Added new "static" endpoints for fetching resources on an agreement directly via HQL, which do not include PTI links Added new minimalView paramter to endpoints going through ermResource view file, which when the class is a PCI will refer instead to a minimalised PCI view file, cutting time off render. ERM-3187 * chore: Removed commented code in view file --- .../SubscriptionAgreementController.groovy | 204 +++++++++++++++++- .../controllers/org/olf/UrlMappings.groovy | 7 + .../views/ermResource/_ermResource.gson | 86 ++++---- .../_minimalPackageContentItem.gson | 36 ++++ service/grails-app/views/pkg/_pkg.gson | 20 +- .../staticCurrentResources.gson | 1 + .../staticDroppedResources.gson | 1 + .../staticFutureResources.gson | 1 + .../staticResources copy.gson | 1 + 9 files changed, 314 insertions(+), 43 deletions(-) create mode 100644 service/grails-app/views/packageContentItem/_minimalPackageContentItem.gson create mode 100644 service/grails-app/views/subscriptionAgreement/staticCurrentResources.gson create mode 100644 service/grails-app/views/subscriptionAgreement/staticDroppedResources.gson create mode 100644 service/grails-app/views/subscriptionAgreement/staticFutureResources.gson create mode 100644 service/grails-app/views/subscriptionAgreement/staticResources copy.gson diff --git a/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy b/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy index 9e32bfb46..7da4181c6 100644 --- a/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy +++ b/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy @@ -8,6 +8,7 @@ import org.olf.kb.ErmResource import org.olf.kb.PackageContentItem import org.olf.kb.Pkg import org.olf.kb.PlatformTitleInstance +import org.olf.erm.Entitlement import com.k_int.okapi.OkapiTenantAwareController @@ -18,6 +19,8 @@ import groovy.util.logging.Slf4j import static org.springframework.http.HttpStatus.* +import java.time.Duration +import java.time.Instant /** * Control access to subscription agreements. @@ -533,7 +536,206 @@ class SubscriptionAgreementController extends OkapiTenantAwareController :today + ) + ) OR + ( + res.removedTimestamp IS NULL AND + pkg_ent.owner.id = :subscriptionAgreementId AND + ( + pkg_ent.activeFrom IS NULL OR + pkg_ent.activeFrom < :today + ) AND + ( + pkg_ent.activeTo IS NULL OR + pkg_ent.activeTo > :today + ) AND + ( + res.accessStart IS NULL OR + res.accessStart < :today + ) AND + ( + res.accessEnd IS NULL OR + res.accessEnd > :today + ) + ) + ${bottomLine} + """.toString(); + case 'dropped': + return """${topLine} + LEFT OUTER JOIN res.entitlements AS direct_ent + LEFT OUTER JOIN res.pkg.entitlements AS pkg_ent + WHERE + ( + direct_ent.owner.id = :subscriptionAgreementId AND + direct_ent.activeTo < :today + ) OR + ( + res.removedTimestamp IS NULL AND + pkg_ent.owner.id = :subscriptionAgreementId AND + ( + res.accessStart IS NULL OR + pkg_ent.activeTo IS NULL OR + res.accessStart < pkg_ent.activeTo + ) AND + ( + res.accessEnd IS NULL OR + pkg_ent.activeFrom IS NULL OR + res.accessEnd > pkg_ent.activeFrom + ) AND + ( + pkg_ent.activeTo < :today OR + res.accessEnd < :today + ) + ) + ${bottomLine} + """.toString(); + case 'future': + return """${topLine} + LEFT OUTER JOIN res.entitlements AS direct_ent + LEFT OUTER JOIN res.pkg.entitlements AS pkg_ent + WHERE + ( + direct_ent.owner.id = :subscriptionAgreementId AND + direct_ent.activeFrom > :today + ) OR + ( + res.removedTimestamp IS NULL AND + pkg_ent.owner.id = :subscriptionAgreementId AND + ( + res.accessStart IS NULL OR + pkg_ent.activeTo IS NULL OR + res.accessStart < pkg_ent.activeTo + ) AND + ( + res.accessEnd IS NULL OR + pkg_ent.activeFrom IS NULL OR + res.accessEnd > pkg_ent.activeFrom + ) AND + ( + pkg_ent.activeFrom > :today OR + res.accessStart > :today + ) + ) + ${bottomLine} + """.toString(); + case 'all': + default: + return """${topLine} WHERE + res.id IN ( + SELECT ent.resource.id FROM Entitlement ent WHERE + ent.owner.id = :subscriptionAgreementId + ) OR + res.id IN ( + SELECT pkg_link.id FROM PackageContentItem as pkg_link WHERE + pkg_link.pkg.id IN ( + SELECT pkg.id FROM Pkg pkg WHERE + pkg.id IN ( + SELECT ent.resource.id FROM Entitlement ent WHERE + ent.owner.id = :subscriptionAgreementId + ) + ) AND + pkg_link.removedTimestamp IS NULL + ${bottomLine} + """.toString() + } + } + + @Transactional(readOnly=true) + private def doStaticResourcesFetch (final String subset = 'all') { + final String subscriptionAgreementId = params.get("subscriptionAgreementId") + final Integer perPage = (params.get("perPage") ?: "10").toInteger(); + + // Funky things will happen if you pass 0 or negative numbers... + final Integer page = (params.get("page") ?: "1").toInteger(); + + if (subscriptionAgreementId) { + // Now + final LocalDate today = LocalDate.now() + final String hql = buildStaticResourceHQL(subscriptionAgreementId, subset); + final List results = PackageContentItem.executeQuery( + hql, + [ + subscriptionAgreementId: subscriptionAgreementId, + today: today + ], + [ + max: perPage, + offset: (page - 1) * perPage + //readOnly: true -- handled in the transaction, no? + ] + ); + + if (params.boolean('stats')) { + final Integer count = PackageContentItem.executeQuery( + buildStaticResourceHQL(subscriptionAgreementId, subset, true), + [ + subscriptionAgreementId: subscriptionAgreementId, + today: today + ] + )[0].toInteger(); + + final def resultsMap = [ + pageSize: perPage, + page: page, + totalPages: ((int)(count / perPage) + (count % perPage == 0 ? 0 : 1)), + meta: [:], // Idk what this is for + totalRecords: count, + total: count, + results: results + ]; + // This method writes to the web request if there is one (which of course there should be as we are in a controller method) + coverageService.lookupCoverageOverrides(resultsMap, "${subscriptionAgreementId}"); + + // respond with full result set + return resultsMap; + } else { + + final def resultsMap = [ results: results ]; + // This method writes to the web request if there is one (which of course there should be as we are in a controller method) + coverageService.lookupCoverageOverrides(resultsMap, "${subscriptionAgreementId}") + + // Respond the list of items + return results + } + } + } + + List staticResources () { + respond doStaticResourcesFetch(); + } + + List staticCurrentResources () { + respond doStaticResourcesFetch('current'); + } + + List staticDroppedResources () { + respond doStaticResourcesFetch('dropped'); + } + + List staticFutureResources () { + respond doStaticResourcesFetch('future'); + } private static final Map> CLONE_GROUPING = [ 'agreementInfo': ['name', 'description', 'renewalPriority' , 'isPerpetual'], diff --git a/service/grails-app/controllers/org/olf/UrlMappings.groovy b/service/grails-app/controllers/org/olf/UrlMappings.groovy index 052bb1c3a..059d206b2 100644 --- a/service/grails-app/controllers/org/olf/UrlMappings.groovy +++ b/service/grails-app/controllers/org/olf/UrlMappings.groovy @@ -23,6 +23,13 @@ class UrlMappings { "/resources/future" (action: 'futureResources', method: 'GET') "/resources/dropped" (action: 'droppedResources', method: 'GET') + // The "static" versions will ONLY return PCIs, not PTIs linked directly (for now) + "/resources/static" (action: 'staticResources', method: 'GET') + "/resources/static/all" (action: 'staticResources', method: 'GET') + "/resources/static/current" (action: 'staticCurrentResources', method: 'GET') + "/resources/static/dropped" (action: 'staticDroppedResources', method: 'GET') + "/resources/static/future" (action: 'staticFutureResources', method: 'GET') + "/resources/export/$format?" (controller: 'export', method: 'GET') "/resources/export/current/$format?" (controller: 'export', action: 'current', method: 'GET') "/resources/export/all/$format?" (controller: 'export', action: 'index', method: 'GET') diff --git a/service/grails-app/views/ermResource/_ermResource.gson b/service/grails-app/views/ermResource/_ermResource.gson index 4c335fc1e..8e8e0212c 100644 --- a/service/grails-app/views/ermResource/_ermResource.gson +++ b/service/grails-app/views/ermResource/_ermResource.gson @@ -14,50 +14,54 @@ Map customCoverageMap = request?.getAttribute("${controllerName}.${actionName}.c // Check for custom coverage on this resource. List customCoverageList = customCoverageMap?.get("${ermResource.id}") -json { - - 'id' ermResource.id - 'class' GrailsHibernateUtil.unwrapIfProxy(ermResource).class.name - 'name' ermResource.getName() - 'suppressFromDiscovery' ermResource.suppressFromDiscovery - 'tags' g.render(ermResource.tags) - 'alternateResourceNames' g.render(ermResource.alternateResourceNames) - - RefdataValue rdv = ermResource.getType() - - if (rdv) { - 'type' g.render (rdv) - } - - if (ermResource.publicationType) { - 'publicationType' g.render (ermResource.publicationType) - } +def minimalView = params.minimalView ?: false +if (minimalView && ermResource.class.name == 'org.olf.kb.PackageContentItem') { + json g.render (template: '/packageContentItem/minimalPackageContentItem', model: (binding.variables + [customCoverageList: customCoverageList])) +} else { + json { + 'id' ermResource.id + 'class' ermResource.class.name + 'name' ermResource.getName() - if (ermResource.subType) { - 'subType' g.render (ermResource.subType) - } - - if (customCoverageList) { + 'suppressFromDiscovery' ermResource.suppressFromDiscovery + 'tags' g.render(ermResource.tags) + 'alternateResourceNames' g.render(ermResource.alternateResourceNames) - 'coverage' g.render (customCoverageList) - 'customCoverage' true + RefdataValue rdv = ermResource.getType() - } else if (ermResource.coverage) { + if (rdv) { + 'type' g.render (rdv) + } - 'coverage' g.render (ermResource.coverage) - 'customCoverage' false - - } else { - 'coverage' [] - 'customCoverage' false - } - - if (params.controller == 'export' ) { - // add extra fields for export - + if (ermResource.publicationType) { + 'publicationType' g.render (ermResource.publicationType) + } + + if (ermResource.subType) { + 'subType' g.render (ermResource.subType) + } + + if (customCoverageList) { + + 'coverage' g.render (customCoverageList) + 'customCoverage' true + + } else if (ermResource.coverage) { + + 'coverage' g.render (ermResource.coverage) + 'customCoverage' false + + } else { + 'coverage' [] + 'customCoverage' false + } + + if (params.controller == 'export' ) { + // add extra fields for export + + } + + //Render the full representation of whatever this object is. + '_object' g.render(ermResource) } - - // Render the full representation of whatever this object is. - '_object' g.render(GrailsHibernateUtil.unwrapIfProxy(ermResource)) - } diff --git a/service/grails-app/views/packageContentItem/_minimalPackageContentItem.gson b/service/grails-app/views/packageContentItem/_minimalPackageContentItem.gson new file mode 100644 index 000000000..05ac959f7 --- /dev/null +++ b/service/grails-app/views/packageContentItem/_minimalPackageContentItem.gson @@ -0,0 +1,36 @@ +import org.olf.kb.PackageContentItem + +import groovy.transform.* + +@Field +PackageContentItem packageContentItem + +@Field +List customCoverageList + +json { + 'tiName' packageContentItem.pti.titleInstance.name + 'platformUrl' packageContentItem.pti.url + 'platformName' packageContentItem.pti.platform.name + + identifiers (packageContentItem.pti.titleInstance.approvedIdentifierOccurrences) {IdentifierOccurrence occurrence -> + identifier g.render(occurrence.identifier) + } + 'pkg' packageContentItem.pkg.name + 'accessStart' packageContentItem.accessStart + 'accessEnd' packageContentItem.accessEnd + + if (customCoverageList) { + 'coverage' g.render (customCoverageList) + 'customCoverage' true + + } else if (packageContentItem.coverage) { + + 'coverage' g.render (packageContentItem.coverage) + 'customCoverage' false + + } else { + 'coverage' [] + 'customCoverage' false + } +} \ No newline at end of file diff --git a/service/grails-app/views/pkg/_pkg.gson b/service/grails-app/views/pkg/_pkg.gson index 0b4fe6e12..63eb392db 100644 --- a/service/grails-app/views/pkg/_pkg.gson +++ b/service/grails-app/views/pkg/_pkg.gson @@ -5,7 +5,25 @@ import groovy.transform.* @Field Pkg pkg -json g.render(pkg, [expand: ['remoteKb','vendor', 'type', 'subType', 'lifecycleStatus', 'availabilityScope', 'packageDescriptionUrls', 'contentTypes', 'alternateResourceNames', 'availabilityConstraints'], excludes: ['contentItems', 'identifiers']]) { +final def should_expand = [ + 'remoteKb', + 'type', + 'subType', + 'lifecycleStatus', + 'availabilityScope', + 'packageDescriptionUrls', + 'contentTypes', + 'alternateResourceNames', + 'availabilityConstraints', + 'vendor' +]; + +final def should_exclude = [ + 'contentItems', + 'identifiers' +]; + +json g.render(pkg, [expand: should_expand, excludes: should_exclude]) { resourceCount pkg.getResourceCount() 'class' Pkg.name diff --git a/service/grails-app/views/subscriptionAgreement/staticCurrentResources.gson b/service/grails-app/views/subscriptionAgreement/staticCurrentResources.gson new file mode 100644 index 000000000..6d3e3a479 --- /dev/null +++ b/service/grails-app/views/subscriptionAgreement/staticCurrentResources.gson @@ -0,0 +1 @@ +json g.render (template: '/resource/resourceList', model: (binding.variables)) \ No newline at end of file diff --git a/service/grails-app/views/subscriptionAgreement/staticDroppedResources.gson b/service/grails-app/views/subscriptionAgreement/staticDroppedResources.gson new file mode 100644 index 000000000..6d3e3a479 --- /dev/null +++ b/service/grails-app/views/subscriptionAgreement/staticDroppedResources.gson @@ -0,0 +1 @@ +json g.render (template: '/resource/resourceList', model: (binding.variables)) \ No newline at end of file diff --git a/service/grails-app/views/subscriptionAgreement/staticFutureResources.gson b/service/grails-app/views/subscriptionAgreement/staticFutureResources.gson new file mode 100644 index 000000000..6d3e3a479 --- /dev/null +++ b/service/grails-app/views/subscriptionAgreement/staticFutureResources.gson @@ -0,0 +1 @@ +json g.render (template: '/resource/resourceList', model: (binding.variables)) \ No newline at end of file diff --git a/service/grails-app/views/subscriptionAgreement/staticResources copy.gson b/service/grails-app/views/subscriptionAgreement/staticResources copy.gson new file mode 100644 index 000000000..6d3e3a479 --- /dev/null +++ b/service/grails-app/views/subscriptionAgreement/staticResources copy.gson @@ -0,0 +1 @@ +json g.render (template: '/resource/resourceList', model: (binding.variables)) \ No newline at end of file From bdf89278a3575d30d7650ee39a72eaa837548502 Mon Sep 17 00:00:00 2001 From: EthanFreestone <54310740+EthanFreestone@users.noreply.github.com> Date: Thu, 23 May 2024 15:00:32 +0100 Subject: [PATCH 2/5] fix: Enrich Title (#769) BaseTIRS enrich title previously checked whether any changes had occurred before saving a TitleInstance. However it was checking eg "book" against "Book", so would _always_ trigger a save. --- .../internal/titleInstanceResolvers/BaseTIRS.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/service/src/main/groovy/org/olf/dataimport/internal/titleInstanceResolvers/BaseTIRS.groovy b/service/src/main/groovy/org/olf/dataimport/internal/titleInstanceResolvers/BaseTIRS.groovy index 772fe3412..e3250f679 100644 --- a/service/src/main/groovy/org/olf/dataimport/internal/titleInstanceResolvers/BaseTIRS.groovy +++ b/service/src/main/groovy/org/olf/dataimport/internal/titleInstanceResolvers/BaseTIRS.groovy @@ -1,5 +1,7 @@ package org.olf.dataimport.internal.titleInstanceResolvers +import com.k_int.web.toolkit.refdata.RefdataValue + import org.olf.IdentifierService import org.olf.dataimport.internal.PackageContentImpl @@ -120,7 +122,7 @@ abstract class BaseTIRS implements TitleInstanceResolverService { * If the "Authoritative" publication type is not equal to whatever mad value a remote site has sent then * replace the authortiative value with the one sent? */ - if (title.publicationType?.value != citation.instancePublicationMedia) { + if (title.publicationType?.value != RefdataValue.normValue(citation.instancePublicationMedia)) { if (citation.instancePublicationMedia) { title.publicationTypeFromString = citation.instancePublicationMedia } else { From 5bb05cdb9e7df3e38859071168c380671f5dd873 Mon Sep 17 00:00:00 2001 From: EthanFreestone <54310740+EthanFreestone@users.noreply.github.com> Date: Fri, 24 May 2024 15:47:52 +0100 Subject: [PATCH 3/5] feat: /static/entitlementOptions (#771) Added new endpoint for entitlementOptions which is _significantly_ faster than the old one refs ERM-3246 --- .../org/olf/ResourceController.groovy | 67 ++++++++++++++++++- .../SubscriptionAgreementController.groovy | 1 + .../controllers/org/olf/UrlMappings.groovy | 1 + .../resource/staticEntitlementOptions.gson | 1 + .../main/okapi/ModuleDescriptor-template.json | 5 ++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 service/grails-app/views/resource/staticEntitlementOptions.gson diff --git a/service/grails-app/controllers/org/olf/ResourceController.groovy b/service/grails-app/controllers/org/olf/ResourceController.groovy index 73591abf9..5b8b1acb0 100644 --- a/service/grails-app/controllers/org/olf/ResourceController.groovy +++ b/service/grails-app/controllers/org/olf/ResourceController.groovy @@ -155,7 +155,72 @@ class ResourceController extends OkapiTenantAwareController { }) log.debug("completed in ${Duration.between(start, Instant.now()).toSeconds()} seconds") } - + + private String buildStaticEntitlementOptionsHQL(Boolean isCount = false) { + return """ + SELECT ${isCount ? 'COUNT(res.id)' : 'res'} FROM ErmResource as res WHERE + res.id IN :flattenedIds + """.toString(); + } + + // I'd like to move this "static fetch" code into a shared space if we get a chance before some kind of OS/ES implementation + @Transactional(readOnly=true) + def staticEntitlementOptions (final String resourceId) { + //final String resourceId = params.get("resourceId") + final Integer perPage = (params.get("perPage") ?: "10").toInteger(); + + // Funky things will happen if you pass 0 or negative numbers... + final Integer page = (params.get("page") ?: "1").toInteger(); + + if (resourceId) { + // Splitting this into two queries to avoid unnecessary joins. Wish we could do this in one but there's + // seemingly no way to flatten results like this + List flattenedIds = PackageContentItem.executeQuery(""" + SELECT pci.id, pci.pkg.id FROM PackageContentItem as pci WHERE + pci.removedTimestamp IS NULL AND + pci.pti.titleInstance.id = :resourceId + """.toString(), [resourceId: resourceId]).flatten() + + final List results = PackageContentItem.executeQuery( + buildStaticEntitlementOptionsHQL(), + [ + flattenedIds: flattenedIds, + ], + [ + max: perPage, + offset: (page - 1) * perPage + //readOnly: true -- handled in the transaction, no? + ] + ); + + if (params.boolean('stats')) { + final Integer count = PackageContentItem.executeQuery( + buildStaticEntitlementOptionsHQL(true), + [ + flattenedIds: flattenedIds, + ] + )[0].toInteger(); + + final def resultsMap = [ + pageSize: perPage, + page: page, + totalPages: ((int)(count / perPage) + (count % perPage == 0 ? 0 : 1)), + meta: [:], // Idk what this is for + totalRecords: count, + total: count, + results: results + ]; + + // respond with full result set + respond resultsMap; + } else { + + // Respond the list of items + respond results + } + } + } + private final Closure entitlementCriteria = { final Class resClass, final ErmResource res -> switch (resClass) { case TitleInstance: diff --git a/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy b/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy index 7da4181c6..bd261f277 100644 --- a/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy +++ b/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy @@ -661,6 +661,7 @@ class SubscriptionAgreementController extends OkapiTenantAwareController Date: Fri, 24 May 2024 19:13:50 +0200 Subject: [PATCH 4/5] =?UTF-8?q?ERM-3187=20Re-write=20query=20to=20show=20"?= =?UTF-8?q?list=20of=20resources=20in=20an=20agreement"=20f=E2=80=A6=20(#7?= =?UTF-8?q?70)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ERM-3187 Re-write query to show "list of resources in an agreement" for improved performance * add missing bracket in default (all) query * add today to query parameters conditionally * rename staticResources.gson * ERM-3187 Re-write query to show "list of resources in an agreement" for improved performance * change current query to cover single future resources from pkg * fix: resource query tweaks Remove "current" additions which change the resultset to something not desired Tweak how the "all" result query works to be more like the others refs ERM-3187 * fix: Split query Splits query into a "fetch all ids", then "fetch resources from list of ids". Slows query down slightly, but removes duplicates from list, and generally is still performant ERM-3187 --------- Co-authored-by: EthanFreestone <54310740+EthanFreestone@users.noreply.github.com> Co-authored-by: Ethan Freestone --- .../SubscriptionAgreementController.groovy | 73 ++++++++++--------- ...sources copy.gson => staticResources.gson} | 0 2 files changed, 38 insertions(+), 35 deletions(-) rename service/grails-app/views/subscriptionAgreement/{staticResources copy.gson => staticResources.gson} (100%) diff --git a/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy b/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy index bd261f277..6b6037415 100644 --- a/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy +++ b/service/grails-app/controllers/org/olf/SubscriptionAgreementController.groovy @@ -537,11 +537,9 @@ class SubscriptionAgreementController extends OkapiTenantAwareController :today ) ) - ${bottomLine} """.toString(); case 'dropped': return """${topLine} @@ -608,7 +605,6 @@ class SubscriptionAgreementController extends OkapiTenantAwareController :today ) ) - ${bottomLine} """.toString(); case 'all': default: - return """${topLine} WHERE - res.id IN ( - SELECT ent.resource.id FROM Entitlement ent WHERE - ent.owner.id = :subscriptionAgreementId - ) OR - res.id IN ( - SELECT pkg_link.id FROM PackageContentItem as pkg_link WHERE - pkg_link.pkg.id IN ( - SELECT pkg.id FROM Pkg pkg WHERE - pkg.id IN ( - SELECT ent.resource.id FROM Entitlement ent WHERE - ent.owner.id = :subscriptionAgreementId - ) - ) AND - pkg_link.removedTimestamp IS NULL - ${bottomLine} + return """${topLine} + LEFT OUTER JOIN res.entitlements AS direct_ent + LEFT OUTER JOIN res.pkg.entitlements AS pkg_ent + WHERE + ( + direct_ent.owner.id = :subscriptionAgreementId + ) OR ( + res.removedTimestamp IS NULL AND + pkg_ent.owner.id = :subscriptionAgreementId + ) """.toString() } } + // Subset can be "all", "current", "dropped" or "future" + // ASSUMES there is a subscription agreement + private String buildStaticResourceHQL(Boolean isCount = false) { + String topLine = """ + SELECT ${isCount ? 'COUNT(res.id)' : 'res'} FROM PackageContentItem as res + WHERE res.id IN :resIds + ${isCount ? '' : 'ORDER BY res.pti.titleInstance.name'} + """.toString(); + } + // I'd like to move this "static fetch" code into a shared space if we get a chance before some kind of OS/ES implementation @Transactional(readOnly=true) private def doStaticResourcesFetch (final String subset = 'all') { @@ -673,13 +672,20 @@ class SubscriptionAgreementController extends OkapiTenantAwareController queryParams = [subscriptionAgreementId: subscriptionAgreementId] + + if (subset in ['current', 'dropped', 'future']) { + queryParams.put('today', today) + } + + final List resIds = PackageContentItem.executeQuery( + buildStaticResourceIdsHQL(subset), + queryParams + ); + final List results = PackageContentItem.executeQuery( - hql, - [ - subscriptionAgreementId: subscriptionAgreementId, - today: today - ], + buildStaticResourceHQL(), + [resIds: resIds], [ max: perPage, offset: (page - 1) * perPage @@ -689,11 +695,8 @@ class SubscriptionAgreementController extends OkapiTenantAwareController Date: Sun, 26 May 2024 21:26:49 +0100 Subject: [PATCH 5/5] chore: Release 6.1.4 Updated NEWS.md file and bumped version -> 6.1.4 refs ERM-3245 --- NEWS.md | 5 +++++ service/gradle.properties | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 3978b9f95..7245d3176 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +## 6.1.4 2024-05-26 + * ERM-3246 Improve performance of entitlementOptions endpoint + * ERM-3187 Re-write query to show "list of resources in an agreement" for improved performance + * Titles previously _always_ enriched, sometimes multiple times, on ingest. Fixed this behaviour where no changes occur + ## 6.1.3 2024-05-08 * ERM-3185 Remove PTI/PCI filter from the /titles/electronic endpoint in Agreements * ERM-3166 On encountering a GOKb title with the same ISSN assigned as both print and electronic ISSN, ingest stops diff --git a/service/gradle.properties b/service/gradle.properties index fd41ba6af..eaa8369ec 100644 --- a/service/gradle.properties +++ b/service/gradle.properties @@ -5,7 +5,7 @@ gorm.version=7.3.3 # Application appName=mod-agreements -appVersion=6.1.3 +appVersion=6.1.4 dockerTagSuffix= dockerRepo=folioci