Skip to content

Commit edb51d2

Browse files
committed
CMR-10672: adds Related-Identifier-With-Type param to citation search
1 parent a68272e commit edb51d2

5 files changed

Lines changed: 202 additions & 6 deletions

File tree

indexer-app/src/cmr/indexer/data/concepts/generic.clj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,33 @@
3939
{(keyword field-name) field-value
4040
(keyword field-name-lower) field-value-lower}))
4141

42+
(defn field->index-complex-field-with-values-only
43+
"Complex indexer field that handles both single objects and arrays,
44+
and formats using field values only (not field names)"
45+
[settings data]
46+
(let [field-list (get settings :Field ".")
47+
field-data (get-in data (generics/jq->list field-list keyword) {})
48+
config (get settings :Configuration {})
49+
sub-fields (get config :sub-fields {})
50+
layout (get config :format "%s=%s")
51+
field-name (util/safe-lowercase (:Name settings))
52+
field-name-lower (str field-name "-lowercase")
53+
field-value (if (vector? field-data)
54+
;; Handle array case
55+
(mapv (fn [element]
56+
(let [values (map #(get element (keyword %)) sub-fields)]
57+
(apply format layout values)))
58+
field-data)
59+
;; Handle single object case
60+
(let [values (map #(get field-data (keyword %)) sub-fields)]
61+
(apply format layout values)))
62+
63+
field-value-lower (if (vector? field-value)
64+
(mapv #(util/safe-lowercase %) field-value)
65+
(util/safe-lowercase field-value))]
66+
{(keyword field-name) field-value
67+
(keyword field-name-lower) field-value-lower}))
68+
4269
(defn field->index-simple-array-field
4370
"The gets the a sub field of an array element and puts those values
4471
into a list so that each value can be searched on one of two indexes:
@@ -85,6 +112,7 @@
85112
[settings data]
86113
(case (:Indexer settings)
87114
"complex-field" (field->index-complex-field settings data)
115+
"complex-fields-only" (field->index-complex-field-with-values-only settings data)
88116
"simple-array-field" (field->index-simple-array-field settings data)
89117
(field->index-default-field settings data)))
90118

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
(ns cmr.indexer.test.data.concepts.generic
2+
(:require
3+
[clojure.test :refer :all]
4+
[cmr.indexer.data.concepts.generic :as generic]))
5+
6+
(def sample-citation-data
7+
{:RelatedIdentifiers [{:RelationshipType "Cites"
8+
:RelatedIdentifier "10.5067/MODIS/MOD08_M3.061"
9+
:RelatedIdentifierType "DOI"}
10+
{:RelationshipType "Describes"
11+
:RelatedIdentifier "ark:/13030/tf1p17542"
12+
:RelatedIdentifierType "ARK"}]
13+
:CitationMetadata {:Title "Global Climate Study"
14+
:Year 2021
15+
:Author [{:Given "John" :Family "Smith" :ORCID "0000-0002-1825-0097"}]}})
16+
17+
(def sample-single-object-data
18+
{:CitationMetadata {:Title "Climate Research"
19+
:Year 2020}})
20+
21+
(def empty-data {})
22+
23+
(defn assert-collections-equal-unordered
24+
"Asserts two collections contain the same elements, ignoring order"
25+
[expected actual]
26+
(is (= (set expected) (set actual))))
27+
28+
(deftest field->index-complex-field-test
29+
(testing "Complex field indexer with field names in format"
30+
(let [settings {:Field ".CitationMetadata"
31+
:Name "Citation-Info"
32+
:Configuration {:sub-fields ["Title" "Year"]
33+
:format "%s=%s"}}
34+
result (generic/field->index-complex-field settings sample-single-object-data)]
35+
(is (= {:citation-info "Title=Climate Research, Year=2020"
36+
:citation-info-lowercase "title=climate research, year=2020"}
37+
result))))
38+
39+
(testing "Complex field with missing data"
40+
(let [settings {:Field ".NonExistentField"
41+
:Name "Missing-Field"
42+
:Configuration {:sub-fields ["Title" "Year"]
43+
:format "%s=%s"}}
44+
result (generic/field->index-complex-field settings sample-single-object-data)]
45+
(is (= {:missing-field "Title=null, Year=null"
46+
:missing-field-lowercase "title=null, year=null"}
47+
result)))))
48+
49+
(deftest field->index-complex-field-with-values-only-test
50+
(testing "Complex field with values only, single object"
51+
(let [settings {:Field ".CitationMetadata"
52+
:Name "Citation-Values"
53+
:Configuration {:sub-fields ["Title" "Year"]
54+
:format "%s:%s"}}
55+
result (generic/field->index-complex-field-with-values-only settings sample-single-object-data)]
56+
(is (= {:citation-values "Climate Research:2020"
57+
:citation-values-lowercase "climate research:2020"}
58+
result))))
59+
60+
(testing "Complex field with values only, array data"
61+
(let [settings {:Field ".RelatedIdentifiers"
62+
:Name "Related-Identifier-With-Type"
63+
:Configuration {:sub-fields ["RelationshipType" "RelatedIdentifier"]
64+
:format "%s:%s"}}
65+
result (generic/field->index-complex-field-with-values-only settings sample-citation-data)]
66+
(is (= {:related-identifier-with-type ["Cites:10.5067/MODIS/MOD08_M3.061"
67+
"Describes:ark:/13030/tf1p17542"]
68+
:related-identifier-with-type-lowercase ["cites:10.5067/modis/mod08_m3.061"
69+
"describes:ark:/13030/tf1p17542"]}
70+
result))))
71+
72+
(testing "Complex field with empty array"
73+
(let [settings {:Field ".EmptyArray"
74+
:Name "Empty-Field"
75+
:Configuration {:sub-fields ["Title" "Year"]
76+
:format "%s:%s"}}
77+
result (generic/field->index-complex-field-with-values-only settings {:EmptyArray []})]
78+
(is (= {:empty-field []
79+
:empty-field-lowercase []}
80+
result)))))
81+
82+
(deftest field->index-simple-array-field-test
83+
(testing "Simple array field extracts values from array elements"
84+
(let [settings {:Field ".RelatedIdentifiers"
85+
:Name "Related-Identifier"
86+
:Configuration {:sub-fields ["RelatedIdentifier"]}}
87+
result (generic/field->index-simple-array-field settings sample-citation-data)]
88+
(assert-collections-equal-unordered
89+
["10.5067/MODIS/MOD08_M3.061" "ark:/13030/tf1p17542"]
90+
(:related-identifier result))
91+
(assert-collections-equal-unordered
92+
["10.5067/modis/mod08_m3.061" "ark:/13030/tf1p17542"]
93+
(:related-identifier-lowercase result))))
94+
95+
(testing "Simple array field with multiple sub-fields"
96+
(let [settings {:Field ".RelatedIdentifiers"
97+
:Name "Relationship-Info"
98+
:Configuration {:sub-fields ["RelationshipType" "RelatedIdentifier"]}}
99+
result (generic/field->index-simple-array-field settings sample-citation-data)]
100+
(assert-collections-equal-unordered
101+
#{"Cites" "10.5067/MODIS/MOD08_M3.061" "Describes" "ark:/13030/tf1p17542"}
102+
(set (:relationship-info result)))))
103+
104+
(testing "Simple array field with nested data"
105+
(let [settings {:Field ".CitationMetadata.Author"
106+
:Name "Author-Info"
107+
:Configuration {:sub-fields ["Given" "Family"]}}
108+
result (generic/field->index-simple-array-field settings sample-citation-data)]
109+
(assert-collections-equal-unordered ["John" "Smith"] (:author-info result))
110+
(assert-collections-equal-unordered ["john" "smith"] (:author-info-lowercase result))))
111+
112+
(testing "Simple array field with empty data"
113+
(let [settings {:Field ".NonExistent"
114+
:Name "Missing-Array"
115+
:Configuration {:sub-fields ["Field1"]}}
116+
result (generic/field->index-simple-array-field settings empty-data)]
117+
(is (= {:missing-array []
118+
:missing-array-lowercase []}
119+
result))))
120+
121+
(deftest field->index-default-field-test
122+
(testing "Default field indexer for simple values"
123+
(let [settings {:Field ".CitationMetadata.Title"
124+
:Name "Title"}
125+
result (generic/field->index-default-field settings sample-citation-data)]
126+
(is (= {:title "Global Climate Study"
127+
:title-lowercase "global climate study"}
128+
result))))
129+
130+
(testing "Default field indexer with nested path"
131+
(let [settings {:Field ".CitationMetadata.Author.0.Given"
132+
:Name "First-Author-Given"}
133+
result (generic/field->index-default-field settings sample-citation-data)]
134+
(is (= {:first-author-given "John"
135+
:first-author-given-lowercase "john"}
136+
result))))
137+
138+
(testing "Default field indexer with missing field"
139+
(let [settings {:Field ".NonExistent.Field"
140+
:Name "Missing-Field"}
141+
result (generic/field->index-default-field settings empty-data)]
142+
(is (= {:missing-field nil
143+
:missing-field-lowercase nil}
144+
result))))))

schemas/resources/schemas/citation/v1.0.0/config.json

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@
4747
"Name": "Resolution-Authority",
4848
"Mapping": "string"
4949
},
50-
{
51-
"Description": "The nature of the relationship between the cited resource and the collection",
52-
"Field": ".RelationshipType",
53-
"Name": "Relationship-Type",
54-
"Mapping": "string"
55-
},
5650
{
5751
"Description": "Science Keywords in keywords",
5852
"Field": ".ScienceKeywords",
@@ -69,6 +63,17 @@
6963
"Indexer": "simple-array-field",
7064
"Configuration": {"sub-fields": ["RelatedIdentifier"]}
7165
},
66+
{
67+
"Description": "Related Identifiers with Relationship Types",
68+
"Field": ".RelatedIdentifiers",
69+
"Name": "Related-Identifier-With-Type",
70+
"Mapping": "string",
71+
"Indexer": "complex-fields-only",
72+
"Configuration": {
73+
"sub-fields": ["RelationshipType", "RelatedIdentifier"],
74+
"format": "%s:%s"
75+
}
76+
},
7277
{
7378
"Description": "Citation Title",
7479
"Field": ".CitationMetadata.Title",

schemas/resources/schemas/citation/v1.0.0/search.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The following parameters can be used to search citations:
3535
* `identifier-type` - Search by identifier type
3636
* `relationship-type` - Search by relationship type
3737
* `related-identifier` - Search by related identifier
38+
* `related-identifier-with-type` - Search for specific relationship-identifier pairs using format `RelationshipType:RelatedIdentifier` (e.g., `Cites:10.5067/SAMPLE/DATA`, `Describes:ark:/13030/tf1p17542`)
3839
* `title` - Search by title
3940
* `year` - Search by publication year (integer)
4041
* `type` - Search by citation type
@@ -218,6 +219,23 @@ __Sample response__
218219
</results>
219220
```
220221

222+
#### Related Identifier Searching
223+
224+
The `related-identifier-with-type` parameter enables searches for citations with specific relationship types to specific identifiers.
225+
226+
__Examples__
227+
228+
```
229+
# Find citations that cite a specific DOI
230+
curl "%CMR-ENDPOINT%/citations?related-identifier-with-type=Cites:10.5067/MODIS/MOD08_M3.061"
231+
232+
# Case-insensitive search
233+
curl "%CMR-ENDPOINT%/citations?related-identifier-with-type-lowercase=cites:10.5067/modis/mod08_m3.061"
234+
235+
# Find all citations that cite something using wildcard option
236+
curl "%CMR-ENDPOINT%/citations?related-identifier-with-type=Cites*&options%5Brelated-identifier-with-type%5D%5Bpattern%5D=true"
237+
```
238+
221239
#### <a name="sorting-citation-results"></a> Sorting Citation Results
222240

223241
By default, Citation results are sorted by name, then by provider-id.

search-app/src/cmr/search/services/parameters/conversion.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
:identifier-type :string
220220
:relationship-type :string
221221
:related-identifier :string
222+
:related-identifier-with-type :string
222223
:title :string
223224
:year :int
224225
:type :string

0 commit comments

Comments
 (0)