Skip to content

Commit 5a0426b

Browse files
committed
feat(packages): show managed and custom subjects in package record
Map HoldingsIQ subjectAssociations to a new read-only subjects array on package data attributes. Managed subjects are always included; custom subjects are included only when explicitly assigned. Refs: MODKBEKBJ-853
1 parent 512d6e6 commit 5a0426b

6 files changed

Lines changed: 163 additions & 0 deletions

File tree

‎NEWS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* Add access types to eholdings/providers/{id}/packages response ([MODKBEKBJ-839](https://folio-org.atlassian.net/browse/MODKBEKBJ-839))
1515
* Sanitize HTML content for the package description field ([MODKBEKBJ-860](https://folio-org.atlassian.net/browse/MODKBEKBJ-860))
1616
* Validate blank package url and remove invalid format checks ([MODKBEKBJ-863](https://folio-org.atlassian.net/browse/MODKBEKBJ-863))
17+
* Add managed and custom subjects to package detail record ([MODKBEKBJ-853](https://folio-org.atlassian.net/browse/MODKBEKBJ-853))
1718

1819
### Bug fixes
1920
* Fix offset handling when retrieving holdings from HoldingsIQ. ([MODKBEKBJ-825](https://folio-org.atlassian.net/browse/MODKBEKBJ-825))

‎ramls/types/packages/packageDataAttributes.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@
121121
"example": 0,
122122
"type": "integer"
123123
},
124+
"subjects": {
125+
"description": "Managed and custom subjects associated with the package",
126+
"items": {
127+
"$ref": "packageSubject.json",
128+
"type": "object"
129+
},
130+
"readonly": true,
131+
"type": "array"
132+
},
124133
"tags": {
125134
"$ref": "../../raml-util/schemas/tags.schema",
126135
"description": "Package tags",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Package Subject Schema",
4+
"description": "Managed or custom subject associated with a package",
5+
"javaType": "org.folio.rest.jaxrs.model.PackageSubject",
6+
"type": "object",
7+
"additionalProperties": false,
8+
"properties": {
9+
"id": {
10+
"description": "Subject id",
11+
"example": 26224,
12+
"type": "integer"
13+
},
14+
"name": {
15+
"description": "Subject name",
16+
"example": "Science",
17+
"type": "string"
18+
},
19+
"schemaId": {
20+
"description": "Subject schema id",
21+
"example": 3,
22+
"type": "integer"
23+
},
24+
"parentId": {
25+
"description": "Parent subject id",
26+
"example": 26224,
27+
"type": "integer"
28+
},
29+
"isCustom": {
30+
"description": "Whether this subject is custom or managed",
31+
"example": false,
32+
"type": "boolean"
33+
},
34+
"subjectHierarchy": {
35+
"description": "Full subject hierarchy path",
36+
"example": "Science -- Biology (General)",
37+
"type": "string"
38+
}
39+
}
40+
}

‎src/main/java/org/folio/rest/converter/packages/PackageCollectionItemConverter.java‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
import static org.folio.rest.converter.packages.PackageConverterUtils.createEmptyPackageRelationship;
66
import static org.folio.rest.util.RestConstants.PACKAGES_TYPE;
77

8+
import java.util.List;
89
import org.folio.holdingsiq.model.AlternateName;
910
import org.folio.holdingsiq.model.PackageData;
11+
import org.folio.holdingsiq.model.SubjectAssociation;
1012
import org.folio.holdingsiq.model.Visibility;
1113
import org.folio.rest.jaxrs.model.Coverage;
1214
import org.folio.rest.jaxrs.model.PackageAltName;
1315
import org.folio.rest.jaxrs.model.PackageCollectionItem;
1416
import org.folio.rest.jaxrs.model.PackageDataAttributes;
17+
import org.folio.rest.jaxrs.model.PackageSubject;
1518
import org.folio.rest.jaxrs.model.PackageVisibility;
19+
import org.jspecify.annotations.Nullable;
1620
import org.springframework.core.convert.converter.Converter;
1721
import org.springframework.stereotype.Component;
1822

@@ -51,11 +55,28 @@ private PackageDataAttributes convertAttributes(PackageData packageData) {
5155
.withProviderName(packageData.getVendorName())
5256
.withProxiedUrl(packageData.getProxiedUrl())
5357
.withSelectedCount(packageData.getSelectedCount())
58+
.withSubjects(convertSubjects(packageData.getSubjectAssociations()))
5459
.withTitleCount(packageData.getTitleCount())
5560
.withUrl(packageData.getPackageUrl())
5661
.withVisibility(mapItemsNullable(packageData.getVisibilityDetails(), this::convertVisibility));
5762
}
5863

64+
private @Nullable List<PackageSubject> convertSubjects(@Nullable List<SubjectAssociation> subjects) {
65+
if (subjects == null) {
66+
return null;
67+
}
68+
return subjects.stream()
69+
.filter(subject -> !Boolean.TRUE.equals(subject.isCustom()) || Boolean.TRUE.equals(subject.explicitAssignment()))
70+
.map(subject -> new PackageSubject()
71+
.withId(subject.id())
72+
.withName(subject.name())
73+
.withSchemaId(subject.schemaId())
74+
.withParentId(subject.parentId())
75+
.withIsCustom(subject.isCustom())
76+
.withSubjectHierarchy(subject.subjectHierarchy()))
77+
.toList();
78+
}
79+
5980
private PackageVisibility convertVisibility(Visibility visibility) {
6081
return new PackageVisibility()
6182
.withCategory(PackageVisibility.Category.fromValue(visibility.category().toUpperCase()))
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.folio.rest.converter.packages;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
7+
import java.util.List;
8+
import org.folio.holdingsiq.model.CoverageDates;
9+
import org.folio.holdingsiq.model.PackageData;
10+
import org.folio.holdingsiq.model.SubjectAssociation;
11+
import org.folio.rest.jaxrs.model.PackageCollectionItem;
12+
import org.folio.spring.config.TestConfig;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.springframework.core.convert.ConversionService;
17+
import org.springframework.test.context.ContextConfiguration;
18+
import org.springframework.test.context.junit.jupiter.SpringExtension;
19+
20+
@ExtendWith(SpringExtension.class)
21+
@ContextConfiguration(classes = TestConfig.class)
22+
class PackageCollectionItemConverterTest {
23+
24+
@Autowired
25+
private ConversionService conversionService;
26+
27+
@Test
28+
void shouldKeepManagedAndExplicitCustomSubjectsAndDropCustomPlaceholders() {
29+
var packageData = PackageData.builder()
30+
.packageId(1)
31+
.vendorId(2)
32+
.contentType("EJournal")
33+
.customCoverage(CoverageDates.builder().beginCoverage("").endCoverage("").build())
34+
.subjectAssociations(List.of(
35+
new SubjectAssociation(1, 3, "Science", false, false, "Science", null),
36+
new SubjectAssociation(2, 3, "Biology", true, false, "Science -- Biology", 1),
37+
new SubjectAssociation(3, 3, "Genetics", true, true, "Science -- Biology -- Genetics", 2),
38+
new SubjectAssociation(4, 3, "Botany", true, true, "Science -- Botany", 1)))
39+
.build();
40+
41+
var subjects = convert(packageData).getAttributes().getSubjects();
42+
43+
assertNotNull(subjects);
44+
assertEquals(List.of("Science", "Genetics", "Botany"),
45+
subjects.stream().map(s -> s.getName()).toList());
46+
var genetics = subjects.get(1);
47+
assertEquals(3, genetics.getId());
48+
assertEquals(3, genetics.getSchemaId());
49+
assertEquals(2, genetics.getParentId());
50+
assertEquals(true, genetics.getIsCustom());
51+
assertEquals("Science -- Biology -- Genetics", genetics.getSubjectHierarchy());
52+
}
53+
54+
@Test
55+
void shouldTreatNullIsCustomAsManaged() {
56+
var packageData = PackageData.builder()
57+
.packageId(1)
58+
.vendorId(2)
59+
.contentType("EJournal")
60+
.customCoverage(CoverageDates.builder().beginCoverage("").endCoverage("").build())
61+
.subjectAssociations(List.of(new SubjectAssociation(1, 3, "Science", null, null, null, null)))
62+
.build();
63+
64+
var subjects = convert(packageData).getAttributes().getSubjects();
65+
66+
assertEquals(1, subjects.size());
67+
}
68+
69+
@Test
70+
void shouldReturnNullSubjectsWhenNoSubjectAssociations() {
71+
var packageData = PackageData.builder().packageId(1).vendorId(2)
72+
.contentType("EJournal")
73+
.customCoverage(CoverageDates.builder().beginCoverage("").endCoverage("").build()).build();
74+
75+
assertNull(convert(packageData).getAttributes().getSubjects());
76+
}
77+
78+
private PackageCollectionItem convert(PackageData packageData) {
79+
var item = conversionService.convert(packageData, PackageCollectionItem.class);
80+
assertNotNull(item);
81+
return item;
82+
}
83+
}

‎src/test/resources/responses/kb-ebsco/packages/expected-package-collection-with-five-elements.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@
3333
"providerId": 392,
3434
"providerName": "American Academy of Family Physicians",
3535
"selectedCount": 0,
36+
"subjects": [
37+
{
38+
"id": 35408,
39+
"name": "Technology",
40+
"schemaId": 3,
41+
"parentId": 35407,
42+
"isCustom": false
43+
}
44+
],
3645
"titleCount": 3,
3746
"visibility": [
3847
{

0 commit comments

Comments
 (0)