Opensearch progress.

- Term search works
- Pending faceted search
- Ongoing: highlight of fuzzy results
This commit is contained in:
Porras-Bernardez 2024-06-07 17:44:13 +02:00
parent 6d1c1b28c3
commit 4f53411d07
5 changed files with 292 additions and 204 deletions

View File

@ -30,5 +30,5 @@ const post = <T>(url: string, body: any, queryParams?: any): Observable<T> => {
.pipe(map((result: AxiosResponse) => result.data)); // Use map to transform the Axios response to extract the data property .pipe(map((result: AxiosResponse) => result.data)); // Use map to transform the Axios response to extract the data property
}; };
// Export the get function as part of the default export // Export the get and post functions as part of the default export
export default { get, post }; export default { get, post };

View File

@ -2,16 +2,14 @@
// import debounce from 'lodash/debounce'; // import debounce from 'lodash/debounce';
// import { DatasetService } from "../../services/dataset.service"; // import { DatasetService } from "../../services/dataset.service";
import DatasetService from "../../services/dataset.service"; import DatasetService from "../../services/dataset.service";
import { SolrSettings } from "@/models/solr"; import { SolrSettings } from "@/models/solr"; // PENDING USE
import { OpenSettings } from "@/models/solr"; import { OpenSettings } from "@/models/solr";
// import { ref } from "vue";
import { Component, Vue, Prop, Emit } from "vue-facing-decorator"; import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
// import { Prop, Emit } from "vue-property-decorator";
import { Dataset, Suggestion, SearchType } from "@/models/dataset"; import { Dataset, Suggestion, SearchType } from "@/models/dataset";
import { SOLR_HOST, SOLR_CORE } from "@/constants"; import { SOLR_HOST, SOLR_CORE } from "@/constants";
import { OPEN_HOST, OPEN_CORE } from "@/constants"; import { OPEN_HOST, OPEN_CORE } from "@/constants"; // PENDING USE
@Component({ @Component({
name: "VsInput", name: "VsInput",
@ -20,19 +18,20 @@ export default class VsInput extends Vue {
// @Prop() // @Prop()
// private title!: string; // private title!: string;
// Define the placeholder text for the input field
@Prop({ default: "Search" }) @Prop({ default: "Search" })
readonly placeholder!: string; readonly placeholder!: string;
private display = ""; private display = ""; // Input display value
@Prop() @Prop()
private propDisplay = ""; private propDisplay = "";
private value!: Suggestion | string; private value!: Suggestion | string;
private error = ""; private error = "";
private results: Array<Dataset> = []; private results: Array<Dataset> = []; // Array to store search results
private loading = false; private loading = false; // Loading state indicator
private selectedIndex = -1; private selectedIndex = -1; // Index of the currently selected suggestion
// private selectedDisplay = ""; // private selectedDisplay = "";
private solr: SolrSettings = { private solr: SolrSettings = {
core: SOLR_CORE, //"rdr_data", // SOLR.core; core: SOLR_CORE, //"rdr_data", // SOLR.core;
@ -49,9 +48,10 @@ export default class VsInput extends Vue {
}; };
// private rdrAPI!: DatasetService; // private rdrAPI!: DatasetService;
itemRefs!: Array<Element>; itemRefs!: Array<Element>; // Array to store references to suggestion items
emits = ["filter"]; emits = ["filter"]; // Emits filter event
// Set reference for each item
setItemRef(el: Element): void { setItemRef(el: Element): void {
this.itemRefs.push(el); this.itemRefs.push(el);
} }
@ -80,6 +80,7 @@ export default class VsInput extends Vue {
return this.error !== null; return this.error !== null;
} }
// Computed property to generate suggestions based on search results
get suggestions(): Suggestion[] { get suggestions(): Suggestion[] {
// const suggestion = { // const suggestion = {
// titles: new Array<string>(), // titles: new Array<string>(),
@ -89,12 +90,17 @@ export default class VsInput extends Vue {
const suggestions = new Array<Suggestion>(); const suggestions = new Array<Suggestion>();
console.log("Display:", this.display); console.log("Display:", this.display);
// console.log("results:", this.results ); console.log("results:", this.results );
// Generate suggestions based on search results
this.results.forEach((dataset) => { this.results.forEach((dataset) => {
console.log("suggestions:foreach:", dataset.id); let foundAny = false;
console.log("get suggestions:id", dataset.id);
console.log("get suggestions:title_output", dataset.title_output);
console.log("get suggestions:author", dataset.author);
console.log("get suggestions:subjects", dataset.subjects);
// const del = dataset.title_output?.toLowerCase(); // const del = dataset.title_output?.toLowerCase();
if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) { if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) {
@ -102,29 +108,40 @@ export default class VsInput extends Vue {
// if (!suggestion["titles"].find((value) => value === title)) { // if (!suggestion["titles"].find((value) => value === title)) {
// suggestion.titles.push(title); // suggestion.titles.push(title);
// } // }
// Check if there is already a suggestion with this title and type
const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title); const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title);
if (!hasTitleSuggestion) { if (!hasTitleSuggestion) {
// If there is no such suggestion, create a new one and add it to the suggestions array
const suggestion = new Suggestion(title, SearchType.Title); const suggestion = new Suggestion(title, SearchType.Title);
suggestions.push(suggestion); suggestions.push(suggestion);
foundAny = true;
}
}
if (this.find(dataset.author, this.display.toLowerCase()) !== "") {
const author = this.find(dataset.author, this.display.toLowerCase());
// Check if there is already a suggestion with this author and type
const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author);
if (!hasAuthorSuggestion) {
const suggestion = new Suggestion(author, SearchType.Author);
suggestions.push(suggestion);
foundAny = true;
}
}
if (this.find(dataset.subjects, this.display.toLowerCase()) != "") {
const subject = this.find(dataset.subjects, this.display.toLowerCase());
const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject);
if (!hasSubjectSuggestion) {
const suggestion = new Suggestion(subject, SearchType.Subject);
suggestions.push(suggestion);
foundAny = true;
} }
} }
// if (this.find(dataset.author, this.display.toLowerCase()) !== "") {
// const author = this.find(dataset.author, this.display.toLowerCase());
// const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author); // if (!foundAny) {
// if (!hasAuthorSuggestion) { // const suggestion = new Suggestion(dataset.title_output, SearchType.Fuzzy);
// const suggestion = new Suggestion(author, SearchType.Author);
// suggestions.push(suggestion); // suggestions.push(suggestion);
// } // }
// }
// if (this.find(dataset.subject, this.display.toLowerCase()) != "") {
// const subject = this.find(dataset.subject, this.display.toLowerCase());
// const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject);
// if (!hasSubjectSuggestion) {
// const suggestion = new Suggestion(subject, SearchType.Subject);
// suggestions.push(suggestion);
// }
// }
}); });
return suggestions; return suggestions;
@ -151,6 +168,7 @@ export default class VsInput extends Vue {
return this.display; return this.display;
} }
// Handler for search input change
searchChanged(): void { searchChanged(): void {
console.log("Search changed!"); console.log("Search changed!");
this.selectedIndex = -1; this.selectedIndex = -1;
@ -164,6 +182,7 @@ export default class VsInput extends Vue {
} }
} }
// Perform the search request
private resourceSearch() { private resourceSearch() {
if (!this.display) { if (!this.display) {
this.results = []; this.results = [];
@ -174,6 +193,7 @@ export default class VsInput extends Vue {
this.request(); this.request();
} }
// Make the API request to search for datasets
private request(): void { private request(): void {
console.log("request()"); console.log("request()");
// DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({ // DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({
@ -184,6 +204,7 @@ export default class VsInput extends Vue {
}); });
} }
// Handle the search results
private dataHandler(datasets: Dataset[]): void { private dataHandler(datasets: Dataset[]): void {
this.results = datasets; this.results = datasets;
// console.log(datasets); // console.log(datasets);
@ -192,6 +213,7 @@ export default class VsInput extends Vue {
// this.loading = false; // this.loading = false;
} }
// Handle errors from the search request
private errorHandler(err: string): void { private errorHandler(err: string): void {
this.error = err; this.error = err;
// this.loading = false; // this.loading = false;
@ -206,6 +228,7 @@ export default class VsInput extends Vue {
return key === this.selectedIndex; return key === this.selectedIndex;
} }
// Handle arrow down key press to navigate suggestions
onArrowDown(ev: Event): void { onArrowDown(ev: Event): void {
ev.preventDefault(); ev.preventDefault();
if (this.selectedIndex === -1) { if (this.selectedIndex === -1) {
@ -216,6 +239,7 @@ export default class VsInput extends Vue {
this.fixScrolling(); this.fixScrolling();
} }
// Scroll the selected suggestion into view
private fixScrolling() { private fixScrolling() {
const currentElement = this.itemRefs[this.selectedIndex]; const currentElement = this.itemRefs[this.selectedIndex];
currentElement.scrollIntoView({ currentElement.scrollIntoView({
@ -225,6 +249,7 @@ export default class VsInput extends Vue {
}); });
} }
// Handle arrow up key press to navigate suggestions
onArrowUp(ev: Event): void { onArrowUp(ev: Event): void {
ev.preventDefault(); ev.preventDefault();
if (this.selectedIndex === -1) { if (this.selectedIndex === -1) {
@ -235,6 +260,7 @@ export default class VsInput extends Vue {
this.fixScrolling(); this.fixScrolling();
} }
// Handle enter key press to select a suggestion
onEnter(): void { onEnter(): void {
if (this.selectedIndex === -1) { if (this.selectedIndex === -1) {
// this.$emit("nothingSelected", this.display); // this.$emit("nothingSelected", this.display);
@ -260,6 +286,7 @@ export default class VsInput extends Vue {
return this.value; return this.value;
} }
// Find a search term in an array
private find(myarray: Array<string>, searchterm: string): string { private find(myarray: Array<string>, searchterm: string): string {
for (let i = 0, len = myarray.length; i < len; i += 1) { for (let i = 0, len = myarray.length; i < len; i += 1) {
if (typeof myarray[i] === "string" && myarray[i].toLowerCase().indexOf(searchterm) !== -1) { if (typeof myarray[i] === "string" && myarray[i].toLowerCase().indexOf(searchterm) !== -1) {

View File

@ -1,27 +1,63 @@
// import moment from "moment"; // import moment from "moment";
import dayjs from "dayjs"; import dayjs from "dayjs";
// // SOLR Dataset original
// export interface Dataset {
// abstract_additional: Array<string>;// OpenSearch: abstract: Array<string>
// abstract_output: string;// -----
// author: Array<string>;// EQUAL
// author_sort: Array<string>;// -----
// belongs_to_bibliography: boolean;// EQUAL
// creating_corporation: string;// EQUAL
// doctype: string;// EQUAL
// geo_location: string;// EQUAL
// id: number;// EQUAL
// identifier: Identifier;// OpenSearch: identifier: Array<string>
// language: string;// EQUAL
// licence: string;// EQUAL
// publisher_name: string;// EQUAL
// server_date_published: Array<number>;// OpenSearch not array!
// subject: Array<string>;// OpenSearch: subjectS
// title_output: string;// EQUAL
// year: number;// EQUAL
// year_inverted: number;// EQUAL
// }
// OpenSearch Dataset
export interface Dataset { export interface Dataset {
abstract_additional: Array<string>; abstract: Array<string>;// OpenSearch: abstract: Array<string>
abstract_output: string; // abstract_output: string;// ----- NOT in OpenSearch
author: Array<string>; author: Array<string>;// EQUAL
author_sort: Array<string>; // author_sort: Array<string>;// ----- NOT in OpenSearch
belongs_to_bibliography: boolean; belongs_to_bibliography: boolean;// EQUAL
creating_corporation: string; creating_corporation: string;// EQUAL
doctype: string; doctype: string;// EQUAL
geo_location: string; geo_location: string;// EQUAL
id: number; id: number;// EQUAL
identifier: Identifier; // identifier: Identifier;// OpenSearch: identifier: Array<string>
language: string; identifier: Array<string>// DIFF DATATYPE
licence: string; language: string;// EQUAL
publisher_name: string; licence: string;// EQUAL
server_date_published: Array<number>; publisher_name: string;// EQUAL
subject: Array<string>; // server_date_published: Array<number>;// OpenSearch string!
title_output: string; server_date_published: string;// DIFF DATATYPE
year: number; // subject: Array<string>;// OpenSearch: subjectS
year_inverted: number; subjects: Array<string>;// DIFF DATATYPE
title_output: string;// EQUAL
year: number;// EQUAL
year_inverted: number;// EQUAL
title: string // Unique in OpenSearch
title_additional: Array<string> // Unique in OpenSearch
bbox_xmin: string // Unique in OpenSearch
bbox_xmax: string // Unique in OpenSearch
bbox_ymin: string // Unique in OpenSearch
bbox_ymax: string // Unique in OpenSearch
reference: Array<string> // Unique in OpenSearch
abstract_additional: Array<string>;// Unique in OpenSearch
} }
export class Suggestion { export class Suggestion {
constructor( constructor(
public value: string, public value: string,
@ -34,39 +70,10 @@ export class Suggestion {
export enum SearchType { export enum SearchType {
Title = "title", Title = "title",
Author = "author", Author = "author",
Subject = "subject", Subject = "subject"
} }
export class DbDataset { export class DbDataset {
// public id!: number;
// public url!: string;
// public contributing_corporation!: string;
// public creating_corporation!: string;
// public publisher_name!: string;
// public embargo_date!: string;
// public publish_id!: number;
// public project_id!: number;
// public type!: string;
// public language!: string;
// public server_state!: string;
// public belongs_to_bibliography!: boolean;
// public created_at!: string;
// public server_date_modified!: string;
// public server_date_published!: string;
// public account_id!: number;
// public editor_id!: number;
// public reviewer_id!: number;
// public preferred_reviewer!: number;
// public preferred_reviewer_email!: string;
// public reject_editor_note!: string;
// public reject_reviewer_note!: string;
// public reviewer_note_visible!: string;
// public titles!: Array<Title>;
// public abstracts!: Array<Abstract>;
// public authors!: Array<Author>;
// public contributors!: Array<Author>;
// public user!: Person;
// public subjects!: Array<Subject>;
constructor( constructor(
public id: string, public id: string,

View File

@ -105,13 +105,29 @@ export interface Hit {
_id: string; _id: string;
_score: number; _score: number;
_source: Dataset; _source: Dataset;
_highlight: HitHighlight; // !! This name is to avoid collision with Typescript "Highlight" class
}
export interface HitHighlight {
subjects?: Array<string>;
title?: Array<string>;
author?: Array<string>;
} }
export interface Aggregations { export interface Aggregations {
[key: string]: Aggregation; subjects: Subjects;
language: Language;
} }
export interface Aggregation { export interface Subjects {
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Array<Bucket>;
}
export interface Language {
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Array<Bucket>; buckets: Array<Bucket>;
} }
@ -119,3 +135,13 @@ export interface Bucket {
key: string; key: string;
doc_count: number; doc_count: number;
} }
// // Needed?
// export interface Aggregations {
// [key: string]: Aggregation;
// }
// export interface Aggregation {
// buckets: Array<Bucket>;
// }

View File

@ -1,7 +1,7 @@
import api from "../api/api"; import api from "../api/api";
// import { Observable, of } from "rxjs"; // import { Observable, of } from "rxjs";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { map } from "rxjs/operators"; import { tap, map } from "rxjs/operators";
import { Dataset, DbDataset, Suggestion } from "@/models/dataset"; import { Dataset, DbDataset, Suggestion } from "@/models/dataset";
import { OpenSearchResponse, SolrResponse } from "@/models/headers"; import { OpenSearchResponse, SolrResponse } from "@/models/headers";
import { ActiveFilterCategories } from "@/models/solr"; import { ActiveFilterCategories } from "@/models/solr";
@ -16,130 +16,130 @@ class DatasetService {
* *
* @param {string} searchTerm - The search term to query. * @param {string} searchTerm - The search term to query.
*/ */
async fetchDataFromOpenSearch(searchTerm: string): Promise<void> { // async fetchDataFromOpenSearch(searchTerm: string): Promise<void> {
// Define the OpenSearch endpoint URL // // Define the OpenSearch endpoint URL
const url = "http://opensearch.geoinformation.dev/tethys-records/_search"; // const url = "http://opensearch.geoinformation.dev/tethys-records/_search";
// Set the headers for the POST request // // Set the headers for the POST request
const headers = { // const headers = {
"Content-Type": "application/json", // "Content-Type": "application/json",
}; // };
// Construct the body of the POST request // // Construct the body of the POST request
const body = { // const body = {
query: { // query: {
bool: { // bool: {
// The `should` clause specifies that at least one of these conditions must match // // The `should` clause specifies that at least one of these conditions must match
should: [ // should: [
{ // {
// Match the search term in the title field with fuzziness enabled and a boost of 3 // // Match the search term in the title field with fuzziness enabled and a boost of 3
match: { // match: {
title: { // title: {
query: searchTerm, // query: searchTerm,
fuzziness: "AUTO", // Enable fuzzy search // fuzziness: "AUTO", // Enable fuzzy search
boost: 3 // Boosting the relevance of title matches // boost: 3 // Boosting the relevance of title matches
} // }
} // }
}, // },
{ // {
// Match the search term in the author field with fuzziness enabled and a boost of 2 // // Match the search term in the author field with fuzziness enabled and a boost of 2
match: { // match: {
author: { // author: {
query: searchTerm, // query: searchTerm,
fuzziness: "AUTO", // Enable fuzzy search // fuzziness: "AUTO", // Enable fuzzy search
boost: 2 // Boosting the relevance of author matches // boost: 2 // Boosting the relevance of author matches
} // }
} // }
}, // },
{ // {
// Match the search term in the subject field with fuzziness enabled and a boost of 1 // // Match the search term in the subject field with fuzziness enabled and a boost of 1
match: { // match: {
subject: { // subject: {
query: searchTerm, // query: searchTerm,
fuzziness: "AUTO", // Enable fuzzy search // fuzziness: "AUTO", // Enable fuzzy search
boost: 1 // Boosting the relevance of subject matches // boost: 1 // Boosting the relevance of subject matches
} // }
} // }
}, // },
{ // {
// Match the search term in the title field with a wildcard // // Match the search term in the title field with a wildcard
wildcard: { // wildcard: {
title: { // title: {
value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm // value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm
boost: 3 // Boosting the relevance of title matches // boost: 3 // Boosting the relevance of title matches
} // }
} // }
}, // },
{ // {
// Match the search term in the author field with a wildcard // // Match the search term in the author field with a wildcard
wildcard: { // wildcard: {
author: { // author: {
value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm // value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm
boost: 2 // Boosting the relevance of author matches // boost: 2 // Boosting the relevance of author matches
} // }
} // }
}, // },
{ // {
// Match the search term in the subject field with a wildcard // // Match the search term in the subject field with a wildcard
wildcard: { // wildcard: {
subject: { // subject: {
value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm // value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm
boost: 1 // Boosting the relevance of subject matches // boost: 1 // Boosting the relevance of subject matches
} // }
} // }
} // }
], // ],
// Ensure that at least one of the `should` clauses must match // // Ensure that at least one of the `should` clauses must match
minimum_should_match: 1 // minimum_should_match: 1
} // }
}, // },
// Limit the number of search results to 10 // // Limit the number of search results to 10
size: 10, // size: 10,
// Start from the first result (pagination) // // Start from the first result (pagination)
from: 0, // from: 0,
// Sort the results by the `server_date_published` field in descending order // // Sort the results by the `server_date_published` field in descending order
sort: [ // sort: [
{ server_date_published: { order: "desc" } } // { server_date_published: { order: "desc" } }
], // ],
// Aggregations to provide facets for the `language` and `subject` fields // // Aggregations to provide facets for the `language` and `subject` fields
aggs: { // aggs: {
language: { // language: {
terms: { // terms: {
field: "language.keyword" // Aggregate by the exact values of the `language` field // field: "language.keyword" // Aggregate by the exact values of the `language` field
} // }
}, // },
subject: { // subject: {
terms: { // terms: {
field: "subjects.keyword", // Aggregate by the exact values of the `subjects` field // field: "subjects.keyword", // Aggregate by the exact values of the `subjects` field
size: 10 // Limit the number of aggregation buckets to 10 // size: 10 // Limit the number of aggregation buckets to 10
} // }
} // }
} // }
}; // };
try { // try {
// Send the POST request to the OpenSearch endpoint // // Send the POST request to the OpenSearch endpoint
const response = await fetch(url, { // const response = await fetch(url, {
method: "POST", // method: "POST",
headers: headers, // headers: headers,
body: JSON.stringify(body), // body: JSON.stringify(body),
}); // });
// Check if the response is not successful // // Check if the response is not successful
if (!response.ok) { // if (!response.ok) {
throw new Error(`Failed to fetch data from ${url}, status: ${response.status}`); // throw new Error(`Failed to fetch data from ${url}, status: ${response.status}`);
} // }
// Parse the response JSON // // Parse the response JSON
const data = await response.json(); // const data = await response.json();
// Log the data from OpenSearch // // Log the data from OpenSearch
console.log("Data from OpenSearch:", data); // console.log("Data from OpenSearch:", data);
console.log("Hits:", data.hits.total.value); // console.log("Hits:", data.hits.total.value);
} catch (error) { // } catch (error) {
// Log any errors that occur during the fetch process // // Log any errors that occur during the fetch process
console.error("Error fetching data:", error); // console.error("Error fetching data:", error);
} // }
} // }
/* https://tethys.at/solr/rdr_data/select?&0=fl%3Did%2Clicence%2Cserver_date_published%2Cabstract_output%2Cidentifier%2Ctitle_output%2Ctitle_additional%2Cauthor%2Csubject%2Cdoctype&q=%2A /* https://tethys.at/solr/rdr_data/select?&0=fl%3Did%2Clicence%2Cserver_date_published%2Cabstract_output%2Cidentifier%2Ctitle_output%2Ctitle_additional%2Cauthor%2Csubject%2Cdoctype&q=%2A
&q.op=or&defType=edismax&qf=title%5E3%20author%5E2%20subject%5E1&indent=on&wt=json&rows=10&start=0&sort=server_date_published%20desc&facet=on&json.facet.language=%7B%20type%3A%20%22 &q.op=or&defType=edismax&qf=title%5E3%20author%5E2%20subject%5E1&indent=on&wt=json&rows=10&start=0&sort=server_date_published%20desc&facet=on&json.facet.language=%7B%20type%3A%20%22
@ -156,25 +156,53 @@ class DatasetService {
should: [ should: [
{ match: { title: { query: term, fuzziness: "AUTO", boost: 3 } } }, { match: { title: { query: term, fuzziness: "AUTO", boost: 3 } } },
{ match: { author: { query: term, fuzziness: "AUTO", boost: 2 } } }, { match: { author: { query: term, fuzziness: "AUTO", boost: 2 } } },
{ match: { subject: { query: term, fuzziness: "AUTO", boost: 1 } } }, { match: { subjects: { query: term, fuzziness: "AUTO", boost: 1 } } }, // In SOLR is "subject"!
{ wildcard: { title: { value: `${term}*`, boost: 3 } } }, { wildcard: { title: { value: `${term}*`, boost: 3 } } },
{ wildcard: { author: { value: `${term}*`, boost: 2 } } }, { wildcard: { author: { value: `${term}*`, boost: 2 } } },
{ wildcard: { subject: { value: `${term}*`, boost: 1 } } } { wildcard: { subjects: { value: `${term}*`, boost: 1 } } } // In SOLR is "subject"!
], ],
minimum_should_match: 1 minimum_should_match: 1
} }
}, },
size: 10, size: 10,
from: 0, from: 0,
sort: [{ server_date_published: { order: "desc" } }], // sort: [{ server_date_published: { order: "desc" } }],
sort: [{ _score: { order: "desc" } }], // Sort by _score in descending order
track_scores: true, // This ensures "_score" is included even when sorting by other criteria. Otherwise the relevance score is not calculated
aggs: { aggs: {
language: { terms: { field: "language.keyword" } }, language: { terms: { field: "language.keyword" } },
subject: { terms: { field: "subjects.keyword", size: 10 } } subjects: { terms: { field: "subjects.keyword", size: 10 } } // In SOLR is "subject"!
},
highlight: {
fields: {
title: {},
author: {},
subjects: {}
}
} }
}; };
// Make API call to OpenSearch and return the result
/**
* When a POST request is made to the OpenSearch server using the api.post<OpenSearchResponse> method, the response received from OpenSearch is an object that includes various details about the search results.
* One of the key properties of this response object is _source, which is an array of documents (datasets) that match the search criteria.
* It is used the pipe method to chain RxJS operators to the Observable returned by api.get. The map operator is used to transform the emitted items of the Observable.
*/
return api.post<OpenSearchResponse>(this.openSearchUrl, body).pipe( return api.post<OpenSearchResponse>(this.openSearchUrl, body).pipe(
// tap(response => console.log("OpenSearchResponse:", response)), // Log the complete response
// tap(response => console.log("Aggre:", response.aggregations?.subjects.buckets[0])), // log the first subject of the array of subjects returned
// tap(response => console.log("Hits:", response.hits)), // log the first subject of the array of subjects returned
map(response => response.hits.hits.map(hit => hit._source)) map(response => response.hits.hits.map(hit => hit._source))
// map(response => response.hits.hits.map(hit => {
// const source = hit._source;
// const highlights = hit._highlight || {};
// return {
// ...source,
// highlights
// };
// }))
); );
} }