- Code cleaning for OpenSearch

- Added comments
- Facets menu small change
This commit is contained in:
Porras-Bernardez 2024-09-12 15:54:59 +02:00
parent 50ab318854
commit 6f1b9f4c5f
5 changed files with 264 additions and 389 deletions

View File

@ -76,7 +76,10 @@ export default FacetCategory;
flex-grow: 1; flex-grow: 1;
flex-shrink: 0; flex-shrink: 0;
/* padding: 0.75rem; */ /* padding: 0.75rem; */
padding: 0.75em 2em; padding-top: 0em;
padding-right: 2em;
padding-bottom: 0.75em;
padding-left: 2em;
justify-content: left; justify-content: left;
} }
@ -89,6 +92,7 @@ export default FacetCategory;
} }
.panel-body { .panel-body {
padding: 0 2em; padding: 0 2em;
padding-bottom: 0.75em; /* Increase padding at the bottom */
} }
.disabled { .disabled {

View File

@ -0,0 +1,161 @@
// public facetedSearchOPEN(
// suggestion: Suggestion | string,
// activeFilterCategories: ActiveFilterCategories,
// openCore: string,
// openHost: string,
// start?: string, // Starting page
// ): Observable<OpenSearchResponse> {
// // OpenSearch endpoint
// const host = openHost;
// const path = "/" + openCore + "/_search";
// const base = host + path;
// // Constructing Filters Based on Active Filter Categories
// const filters = Object.entries(activeFilterCategories).map(([category, values]) => {
// if (category === "language" || category === "year") {
// return { terms: { [category]: values } };
// } else {
// return { terms: { [`${category}.keyword`]: values } };
// }
// });
// console.log("filters:", filters);
// // Determine search term and query fields based on the suggestion type
// let query;
// if (typeof suggestion === "string") { // If suggestion is a string, append a wildcard (*) for partial matches
// const lowercaseTerm = suggestion.toLowerCase()
// query = {
// bool: {
// should: [
// { match: { title: { query: suggestion, fuzziness: "AUTO", boost: 3 } } },
// { match: { author: { query: suggestion, fuzziness: "AUTO", boost: 2 } } },
// { match: { subjects: { query: suggestion, fuzziness: "AUTO", boost: 1 } } },
// { wildcard: { title: { value: `${lowercaseTerm}*`, boost: 3 } } },
// { wildcard: { author: { value: `${lowercaseTerm}*`, boost: 2 } } },
// { wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// };
// } else if (suggestion instanceof Suggestion) { // If suggestion is a Suggestion object, form a specific query
// query = {
// match: {
// [suggestion.type]: {
// query: suggestion.value,
// operator: 'and' // all the terms in the query must be present in the field e.g. if is a title, the complete title
// }
// }
// };
// }
// // Set default value for start if not provided
// const startValue = start ? parseInt(start) : 0;
// // console.log(activeFilterCategories);
// // console.log("mainQuery:", mainQuery);
// // Construct the body of the OpenSearch query
// const body = {
// query: {
// bool: {
// must: [
// mainQuery, // Ensure the main query must be satisfied
// ...filters // Ensure all filters must be satisfied
// ]
// }
// },
// // // WORKS // Expected: 12 results
// // query: {
// // bool: {
// // "must": [
// // { "term": { "language": "en" } },
// // { "term": { "subjects.keyword": "Lower Austria" } },
// // { "term": { "subjects.keyword": "counting data" } }
// // ],
// // }
// // },
// // // THIS WORKS: // Expected: 19 results
// // query: {
// // bool: {
// // must: [
// // { "match": { "title": "Blatt" } },
// // { "term": { "subjects": "bayern" } }
// // ],
// // }
// // },
// // // WORKS // Expected: 4 results
// // query: {
// // bool: {
// // "must": [
// // { "match": { "title": "blatt" } },
// // { "term": { "subjects": "bayern" } },
// // { "term": { "subjects": "salzburg" } }
// // ],
// // }
// // },
// // // WORKS // Expected: 2 results
// // query: {
// // bool: {
// // "must": [
// // { "match": { "title": "blatt" } },
// // { "term": { "subjects": "ungarn" } },
// // { "term": { "subjects": "steiermark" } }
// // ],
// // }
// // },
// // WORKS // Expected: 12 results
// query: {
// bool: {
// "must": [
// { "term": { "language": "en" } },
// { "term": { "subjects.keyword": "Lower Austria" } },
// { "term": { "subjects.keyword": "counting data" } }
// ],
// "should": [
// { match: { title: { query: "halger", fuzziness: "AUTO", boost: 3 } } },
// { match: { author: { query: "halger", fuzziness: "AUTO", boost: 2 } } },
// { match: { subjects: { query: "halger", fuzziness: "AUTO", boost: 1 } } },
// { wildcard: { title: { value: "halger", boost: 3 } } },
// { wildcard: { author: { value: "halger", boost: 2 } } },
// { wildcard: { subjects: { value: "halger", boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// },
// size: 10,
// from: startValue,
// sort: [{ _score: { order: "desc" } }],
// track_scores: true,
// aggs: {
// subjects: { terms: { field: "subjects.keyword", size: 1000 } },
// language: { terms: { field: "language" } },
// author: { terms: { field: "author.keyword", size: 1000 } },
// year: { terms: { field: "year", size: 100 } }
// },
// highlight: {
// fields: {
// title: {},
// author: {},
// subjects: {}
// }
// }
// };
// console.log("mainQuery:", mainQuery);
// console.log("filters:", filters);
// console.log("body:", body);
// // Make API call to OpenSearch and return the result
// const stations = api.post<OpenSearchResponse>(base, body);
// return stations;
// }

View File

@ -1,5 +1,4 @@
import api from "../api/api"; import api from "../api/api";
// import { Observable, of } from "rxjs";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { tap, map } from "rxjs/operators"; import { tap, map } from "rxjs/operators";
import { Dataset, DbDataset, Suggestion } from "@/models/dataset"; import { Dataset, DbDataset, Suggestion } from "@/models/dataset";
@ -10,33 +9,23 @@ import { deserialize } from "class-transformer";
class DatasetService { class DatasetService {
/** /**
* Fetch data from the OpenSearch endpoint with fuzzy search enabled. * Search datasets with OpenSearch API, allowing for fuzzy search and boosting relevance in title, author, and subject fields.
* This function allows for misspellings in the search term and boosts * @param {string} searchTerm - Search query term
* the relevance of matches in the title, author, and subject fields. * @param {string} openCore - The OpenSearch core to search in
* * @param {string} openHost - The OpenSearch host URL
* @param {string} searchTerm - The search term to query. * @returns {Observable} - Observable emitting datasets and their highlights
*/ */
/* 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
terms%22%2C%20field%3A%20%22language%22%20%7D&json.facet.subject=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22subject%22%2C%20limit%3A%20-1%20%7D&json.facet.year=%7B%20type%3A%20%22
terms%22%2C%20field%3A%20%22year%22%20%7D&json.facet.author=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22author_facet%22%2C%20limit%3A%20-1%20%7D
*/
// private openSearchUrl = "http://opensearch.geoinformation.dev/tethys-records/_search";
// private openSearchUrl = "http://192.168.21.18/tethys-records/_search";
public searchTerm(term: string, openCore: string, openHost: string): Observable<{ datasets: Dataset[], highlights: HitHighlight[] }> { public searchTerm(term: string, openCore: string, openHost: string): Observable<{ datasets: Dataset[], highlights: HitHighlight[] }> {
// console.log("SEARCHTERM");
const host = openHost; // OpenSearch host URL
// OpenSearch endpoint const path = "/" + openCore + "/_search"; // API endpoint for searching
const host = openHost; // When using local OpenSearch dev endpoint const base = host + path; // Complete URL for the request
const path = "/" + openCore + "/_search";
const base = host + path;
/** /**
* The match query used for title, author, and subjects fields is case-insensitive by default. The standard analyzer is typically used, which lowercases the terms. * The match query used for title, author, and subjects fields is case-insensitive by default. The standard analyzer is typically used, which lowercases the terms.
* The wildcard query is case-sensitive by default. To make it case-insensitive, it is needed to use a lowercase filter */ * The wildcard query is case-sensitive by default. To make it case-insensitive, it is needed to use a lowercase filter */
const lowercaseTerm = term.toLowerCase(); // Lowercase the search term const lowercaseTerm = term.toLowerCase(); // Lowercase the search term
// Request body defining search query logic
const body = { const body = {
query: { query: {
bool: { bool: {
@ -50,13 +39,13 @@ class DatasetService {
{ wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } }, // In SOLR is "subject"! { wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } }, // In SOLR is "subject"!
{ wildcard: { doctype: { value: `${lowercaseTerm}*`, boost: 1 } } } // doctype { wildcard: { doctype: { value: `${lowercaseTerm}*`, boost: 1 } } } // doctype
], ],
minimum_should_match: 1 minimum_should_match: 1 // Require at least one match
} }
}, },
size: 10, size: 10, // Limit to 10 results
from: 0, from: 0, // Pagination: start from the first result
sort: [{ _score: { order: "desc" } }], // Sort by relevance (_score)
// 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 track_scores: true, // This ensures "_score" is included even when sorting by other criteria. Otherwise the relevance score is not calculated
aggs: { aggs: {
subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"! subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"!
@ -65,37 +54,23 @@ class DatasetService {
year: { terms: { field: "year", size: 100 } }, // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS year: { terms: { field: "year", size: 100 } }, // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
doctype: { terms: { field: "doctype", size: 50 } } // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS doctype: { terms: { field: "doctype", size: 50 } } // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
}, },
// // CONTABO ================================================================================
// aggs: {
// subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"!
// language: { terms: { field: "language.keyword" } }, // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
// author: { terms: { field: "author.keyword", size: 1000 } },
// year: { terms: { field: "year.keyword", size: 100 } } // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
// },
// // ===========================================================================================
highlight: { highlight: {
fields: { fields: {
title: {}, title: {}, // Highlight matching terms in title
author: {}, author: {}, // Highlight matching terms in author
subjects: {}, subjects: {}, // Highlight matching terms in subjects
doctype: {} doctype: {} // Highlight matching terms in document type
} }
} }
}; };
// Make API call to OpenSearch and return the result
/** /**
* 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. * 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. * 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. * 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>(base, body).pipe( return api.post<OpenSearchResponse>(base, 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 => ({ map(response => ({
datasets: response.hits.hits.map(hit => hit._source), datasets: response.hits.hits.map(hit => hit._source),
highlights: response.hits.hits.map(hit => hit.highlight) highlights: response.hits.hits.map(hit => hit.highlight)
@ -104,7 +79,7 @@ class DatasetService {
} }
// // For the autocomplete search. Method to perform a search based on a term // // For the autocomplete search. Method to perform a search based on a term
// public searchTerm_SOLR(term: string, solrCore: string, solrHost: string): Observable<Dataset[]> { // public searchTermSOLR(term: string, solrCore: string, solrHost: string): Observable<Dataset[]> {
// // SOLR endpoint // // SOLR endpoint
// const host = "https://" + solrHost; // const host = "https://" + solrHost;
// const path = "/solr/" + solrCore + "/select?"; // const path = "/solr/" + solrCore + "/select?";
@ -123,7 +98,6 @@ class DatasetService {
// "doctype", // "doctype",
// ].toString(); // ].toString();
// const qfFields = "title^3 author^2 subject^1"; // const qfFields = "title^3 author^2 subject^1";
// const q_params = { // const q_params = {
@ -146,193 +120,33 @@ class DatasetService {
// return stations; // return stations;
// } // }
// public facetedSearchOPEN(
// suggestion: Suggestion | string,
// activeFilterCategories: ActiveFilterCategories,
// openCore: string,
// openHost: string,
// start?: string, // Starting page
// ): Observable<OpenSearchResponse> {
// // OpenSearch endpoint
// const host = openHost;
// const path = "/" + openCore + "/_search";
// const base = host + path;
// // Constructing Filters Based on Active Filter Categories
// const filters = Object.entries(activeFilterCategories).map(([category, values]) => {
// if (category === "language" || category === "year") {
// return { terms: { [category]: values } };
// } else {
// return { terms: { [`${category}.keyword`]: values } };
// }
// });
// console.log("filters:", filters);
// // Determine search term and query fields based on the suggestion type /**
// let query; * Perform faceted search with OpenSearch API using filters and suggestions
// if (typeof suggestion === "string") { // If suggestion is a string, append a wildcard (*) for partial matches * @param {Suggestion | string} suggestion - Search term or suggestion
// const lowercaseTerm = suggestion.toLowerCase() * @param {ActiveFilterCategories} activeFilterCategories - Active filters to apply
// query = { * @param {string} openCore - The OpenSearch core to search in
// bool: { * @param {string} openHost - The OpenSearch host URL
// should: [ * @param {string} start - Optional: starting page
// { match: { title: { query: suggestion, fuzziness: "AUTO", boost: 3 } } }, * @returns {Observable<OpenSearchResponse>} - Observable emitting search results
// { match: { author: { query: suggestion, fuzziness: "AUTO", boost: 2 } } }, */
// { match: { subjects: { query: suggestion, fuzziness: "AUTO", boost: 1 } } }, public facetedSearch(
// { wildcard: { title: { value: `${lowercaseTerm}*`, boost: 3 } } },
// { wildcard: { author: { value: `${lowercaseTerm}*`, boost: 2 } } },
// { wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// };
// } else if (suggestion instanceof Suggestion) { // If suggestion is a Suggestion object, form a specific query
// query = {
// match: {
// [suggestion.type]: {
// query: suggestion.value,
// operator: 'and' // all the terms in the query must be present in the field e.g. if is a title, the complete title
// }
// }
// };
// }
// // Set default value for start if not provided
// const startValue = start ? parseInt(start) : 0;
// // console.log(activeFilterCategories);
// // console.log("mainQuery:", mainQuery);
// // Construct the body of the OpenSearch query
// const body = {
// query: {
// bool: {
// must: [
// mainQuery, // Ensure the main query must be satisfied
// ...filters // Ensure all filters must be satisfied
// ]
// }
// },
// // // WORKS // Expected: 12 results
// // query: {
// // bool: {
// // "must": [
// // { "term": { "language": "en" } },
// // { "term": { "subjects.keyword": "Lower Austria" } },
// // { "term": { "subjects.keyword": "counting data" } }
// // ],
// // }
// // },
// // // THIS WORKS: // Expected: 19 results
// // query: {
// // bool: {
// // must: [
// // { "match": { "title": "Blatt" } },
// // { "term": { "subjects": "bayern" } }
// // ],
// // }
// // },
// // // WORKS // Expected: 4 results
// // query: {
// // bool: {
// // "must": [
// // { "match": { "title": "blatt" } },
// // { "term": { "subjects": "bayern" } },
// // { "term": { "subjects": "salzburg" } }
// // ],
// // }
// // },
// // // WORKS // Expected: 2 results
// // query: {
// // bool: {
// // "must": [
// // { "match": { "title": "blatt" } },
// // { "term": { "subjects": "ungarn" } },
// // { "term": { "subjects": "steiermark" } }
// // ],
// // }
// // },
// // WORKS // Expected: 12 results
// query: {
// bool: {
// "must": [
// { "term": { "language": "en" } },
// { "term": { "subjects.keyword": "Lower Austria" } },
// { "term": { "subjects.keyword": "counting data" } }
// ],
// "should": [
// { match: { title: { query: "halger", fuzziness: "AUTO", boost: 3 } } },
// { match: { author: { query: "halger", fuzziness: "AUTO", boost: 2 } } },
// { match: { subjects: { query: "halger", fuzziness: "AUTO", boost: 1 } } },
// { wildcard: { title: { value: "halger", boost: 3 } } },
// { wildcard: { author: { value: "halger", boost: 2 } } },
// { wildcard: { subjects: { value: "halger", boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// },
// size: 10,
// from: startValue,
// sort: [{ _score: { order: "desc" } }],
// track_scores: true,
// aggs: {
// subjects: { terms: { field: "subjects.keyword", size: 1000 } },
// language: { terms: { field: "language" } },
// author: { terms: { field: "author.keyword", size: 1000 } },
// year: { terms: { field: "year", size: 100 } }
// },
// highlight: {
// fields: {
// title: {},
// author: {},
// subjects: {}
// }
// }
// };
// console.log("mainQuery:", mainQuery);
// console.log("filters:", filters);
// console.log("body:", body);
// // Make API call to OpenSearch and return the result
// const stations = api.post<OpenSearchResponse>(base, body);
// return stations;
// }
public facetedSearchOPEN(
suggestion: Suggestion | string, suggestion: Suggestion | string,
activeFilterCategories: ActiveFilterCategories, activeFilterCategories: ActiveFilterCategories,
openCore: string, openCore: string,
openHost: string, openHost: string,
start?: string, // Starting page start?: string, // Starting page
): Observable<OpenSearchResponse> { ): Observable<OpenSearchResponse> {
// console.log("FACETEDSEARCH");
// OpenSearch endpoint
const host = openHost; const host = openHost;
const path = "/" + openCore + "/_search"; const path = "/" + openCore + "/_search";
const base = host + path; const base = host + path;
const lowercaseTerm = typeof suggestion === 'string' ? suggestion.toLowerCase() : suggestion.value; const lowercaseTerm = typeof suggestion === 'string' ? suggestion.toLowerCase() : suggestion.value;
// console.log("facetedsearchOPEN > suggestion entered:");
// console.log(suggestion);
// console.log("typeof:", typeof suggestion);
/** /**
* The query construction depends on whether the suggestion is a string or a Suggestion object. */ * The query construction depends on whether the suggestion is a string or a Suggestion object.
* */
// When suggestion is a string: // When suggestion is a string:
const mainQuery = typeof suggestion === 'string' const mainQuery = typeof suggestion === 'string'
? { ? {
@ -350,7 +164,7 @@ class DatasetService {
minimum_should_match: 1 minimum_should_match: 1
} }
} }
// When suggestion is a suggestion object // When suggestion is a suggestion object:
: { : {
match: { match: {
[suggestion.type]: { [suggestion.type]: {
@ -360,28 +174,7 @@ class DatasetService {
} }
}; };
// CONTABO ==================================================== // Build filters based on the active filter categories
// // Constructing Filters Based on Active Filter Categories
// const filters = Object.entries(activeFilterCategories).map(([category, values]) => ({
// terms: { [`${category}.keyword`]: values }
// }));
// ================================================================
// // Constructing Filters Based on Active Filter Categories
// const filters = Object.entries(activeFilterCategories).map(([category, values]) => {
// if (category === "language" || category === "year") {
// return { terms: { [category]: values } };
// } else {
// return { terms: { [`${category}.keyword`]: values } };
// }
// });
// const filters = Object.entries(activeFilterCategories).map(([category, values]) =>
// values.map(value => ({ term: { [`${category}.keyword`]: value } }))
// ).flat();
const filters = Object.entries(activeFilterCategories).map(([category, values]) => { const filters = Object.entries(activeFilterCategories).map(([category, values]) => {
if (category === "language" || category === "year" || category === "doctype") { if (category === "language" || category === "year" || category === "doctype") {
return values.map(value => ({ term: { [category]: value } })); return values.map(value => ({ term: { [category]: value } }));
@ -390,22 +183,8 @@ class DatasetService {
} }
}).flat(); }).flat();
// console.log(activeFilterCategories); // Request body for the faceted search
console.log("mainQuery:", mainQuery);
console.log("filters:", filters);
const body = { const body = {
// query: {
// bool: {
// must: query, // Contains the main query constructed earlier.
// filter: filters // Contains the filters constructed from activeFilterCategories.
// // filter: [
// // { "terms": { "subjects.keyword": ["Lower Austria", "counting data"] } },
// // { "term": { "language": "en" } }
// // ] // Contains the filters constructed from activeFilterCategories.
// }
// },
query: { query: {
bool: { bool: {
must: [ must: [
@ -415,49 +194,24 @@ class DatasetService {
} }
}, },
// // THIS DOESNT WORK // Expected: 12 results, Ouput: 16
// query: {
// bool: {
// "must": [
// { "term": { "language": "en" } },
// { "terms": { "subjects.keyword": ["Lower Austria", "counting data"] } }
// ],
// "should": [
// { match: { title: { query: "halger", fuzziness: "AUTO", boost: 3 } } },
// { match: { author: { query: "halger", fuzziness: "AUTO", boost: 2 } } },
// { match: { subjects: { query: "halger", fuzziness: "AUTO", boost: 1 } } },
// { wildcard: { title: { value: "halger", boost: 3 } } },
// { wildcard: { author: { value: "halger", boost: 2 } } },
// { wildcard: { subjects: { value: "halger", boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// },
size: 10, size: 10,
from: start ? parseInt(start) : 0, from: start ? parseInt(start) : 0,
// sort: [{ _source: { server_date_published: { order: "desc" } } }], sort: [{ server_date_published: { order: "desc" } }], // Sort by publication date
sort: [{ server_date_published: { order: "desc" } }],
// sort: [{ _score: { order: "desc" } }], // Sort by _score in descending order // sort: [{ _score: { order: "desc" } }], // Sort by _score in descending order
track_scores: true, track_scores: true,
aggs: { // Defines aggregations for facets /**
// terms: Aggregation type that returns the most common terms in a field. * Defines aggregations for facets
// !For a large number of terms setting an extremely large size might not be efficient * terms: Aggregation type that returns the most common terms in a field.
// If you genuinely need all unique terms and expect a large number of them, consider using a composite aggregation for more efficient pagination of terms. * !For a large number of terms setting an extremely large size might not be efficient
* If you genuinely need all unique terms and expect a large number of them, consider using a composite aggregation for more efficient pagination of terms.
*/
aggs: {
subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"! subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"!
language: { terms: { field: "language" } }, // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS language: { terms: { field: "language" } }, // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
author: { terms: { field: "author.keyword", size: 1000 } }, author: { terms: { field: "author.keyword", size: 1000 } },
year: { terms: { field: "year", size: 100 } }, // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS year: { terms: { field: "year", size: 100 } }, // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
doctype: { terms: { field: "doctype", size: 50 } } // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS doctype: { terms: { field: "doctype", size: 50 } } // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
}, },
// CONTABO ================================================================================
// aggs: {
// subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"!
// language: { terms: { field: "language.keyword" } }, // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
// author: { terms: { field: "author.keyword", size: 1000 } },
// year: { terms: { field: "year.keyword", size: 100 } } // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
// },
// ===========================================================================================
highlight: { highlight: {
fields: { fields: {
title: {}, title: {},
@ -468,17 +222,15 @@ class DatasetService {
} }
}; };
// console.log("body:", body); // API call and return observable of search results
const stations = api.post<OpenSearchResponse>(base, body); const stations = api.post<OpenSearchResponse>(base, body);
return stations; return stations;
} }
// /** // /**
// * This method performs a faceted search on a Solr core. Faceted search allows the user to filter search results based on various categories (facets) // * This method performs a faceted search on a Solr core. Faceted search allows the user to filter search results based on various categories (facets)
// */ // */
// public facetedSearch_SOLR( // public facetedSearchSOLR(
// suggestion: Suggestion | string, // suggestion: Suggestion | string,
// activeFilterCategories: ActiveFilterCategories, // activeFilterCategories: ActiveFilterCategories,
// solrCore: string, // solrCore: string,
@ -609,8 +361,8 @@ class DatasetService {
const host = VUE_API; const host = VUE_API;
const path = "/api/dataset/" + id; const path = "/api/dataset/" + id;
const apiUrl = host + path; const apiUrl = host + path;
const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
return dataset; return dataset;
} }
@ -619,16 +371,16 @@ class DatasetService {
const host = VUE_API; const host = VUE_API;
const path = "/api/dataset/10.24341/tethys." + doi; const path = "/api/dataset/10.24341/tethys." + doi;
const apiUrl = host + path; const apiUrl = host + path;
const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
return dataset; return dataset;
} }
// Method to prepare dataset object // Prepare dataset object by deserializing it and adding a URL
private prepareDataset(datasetObj: DbDataset): DbDataset { private prepareDataset(datasetObj: DbDataset): DbDataset {
const dataset = deserialize<DbDataset>(DbDataset, JSON.stringify(datasetObj)); const dataset = deserialize<DbDataset>(DbDataset, JSON.stringify(datasetObj));
dataset.url = document.documentURI; dataset.url = document.documentURI;
return dataset; return dataset;
} }
} }

View File

@ -1,17 +1,18 @@
// Import necessary modules, components, and models from Vue and the project
import { Component, Vue, Prop } from "vue-facing-decorator"; import { Component, Vue, Prop } from "vue-facing-decorator";
import VsInput from "@/components/vs-input/vs-input.vue"; import VsInput from "@/components/vs-input/vs-input.vue";
import VsResult from "@/components/vs-result/vs-result.vue"; import VsResult from "@/components/vs-result/vs-result.vue";
import FacetCategory from "@/components/face-category/facet-category.vue"; import FacetCategory from "@/components/face-category/facet-category.vue";
import ActiveFacetCategory from "@/components/active-facet-category/active-facet-category.vue"; import ActiveFacetCategory from "@/components/active-facet-category/active-facet-category.vue";
// import { SolrSettings } from "@/models/solr";
// Import models and services
// import { SolrSettings } from "@/models/solr";
import { OpenSettings } from "@/models/solr"; import { OpenSettings } from "@/models/solr";
// import { DatasetService } from "@/services/dataset.service";
import DatasetService from "../../services/dataset.service"; import DatasetService from "../../services/dataset.service";
import { Suggestion, Dataset, SearchType } from "@/models/dataset"; import { Suggestion, Dataset, SearchType } from "@/models/dataset";
// import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance } from "@/models/headers"; // import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance } from "@/models/headers";
// import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance, OpenSearchResponse, HitHighlight } from "@/models/headers"; // import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance, OpenSearchResponse, HitHighlight } from "@/models/headers";
import { FacetFields, FacetItem, FacetResults, FacetInstance, OpenSearchResponse, HitHighlight } from "@/models/headers"; import { FacetItem, FacetResults, OpenSearchResponse } from "@/models/headers";
import { ActiveFilterCategories } from "@/models/solr"; import { ActiveFilterCategories } from "@/models/solr";
// import { SOLR_HOST, SOLR_CORE } from "@/constants"; // import { SOLR_HOST, SOLR_CORE } from "@/constants";
import { IPagination } from "@/models/pagination"; import { IPagination } from "@/models/pagination";
@ -19,7 +20,7 @@ import PaginationComponent from "@/components/PaginationComponent.vue";
import { OPEN_HOST, OPEN_CORE } from "@/constants"; import { OPEN_HOST, OPEN_CORE } from "@/constants";
// Decorate the component and define its name and components // Define the Vue component, its name, and child components
@Component({ @Component({
name: "SearchViewComponent", name: "SearchViewComponent",
components: { components: {
@ -31,51 +32,51 @@ import { OPEN_HOST, OPEN_CORE } from "@/constants";
}, },
}) })
// Define the SearchViewComponent class // Export the default class for the component
export default class SearchViewComponent extends Vue { export default class SearchViewComponent extends Vue {
@Prop() // Define props passed from the parent component
display!: string; @Prop()
display!: string; // Search display string
@Prop() @Prop()
type!: string; type!: string; // Search type
results: Array<Dataset> = []; // Declare variables used in the component
results: Array<Dataset> = []; // Array to hold search results
// facets: FacetFields = new FacetFields(); facets: FacetResults = new FacetResults(); // Object to hold facet results
facets: FacetResults = new FacetResults(); searchTerm: string | Suggestion = ""; // The search term input
searchTerm: string | Suggestion = ""; activeFilterCategories: ActiveFilterCategories = new ActiveFilterCategories(); // Active filter categories for search
// activeFilterCategories: Object = {}; pagination: IPagination = { // Pagination data for the results
activeFilterCategories: ActiveFilterCategories = new ActiveFilterCategories(); // = new Array<ActiveFilterCategory>();
pagination: IPagination = {
total: 0, total: 0,
perPage: 10, perPage: 10,
currentPage: 1, currentPage: 1,
// lastPage: 0,
data: [], data: [],
}; };
loaded = false; loaded = false; // Boolean to track whether data has been loaded
numFound!: number; numFound!: number; // Number of results found
// private solr: SolrSettings = { // private solr: SolrSettings = {
// core: SOLR_CORE, //"rdr_data", // SOLR.core; // core: SOLR_CORE, //"rdr_data", // SOLR.core;
// host: SOLR_HOST, //"tethys.at", // host: SOLR_HOST, //"tethys.at",
// }; // };
// Define settings for the OpenSearch API (core and host information)
private open: OpenSettings = { private open: OpenSettings = {
core: OPEN_CORE, //"rdr_data", // SOLR.core; core: OPEN_CORE, //
host: OPEN_HOST, //"tethys.at", host: OPEN_HOST, //
}; };
private error = ""; private error = "";
// Computed property to get search term as string // Computed property to get search term as string
get stringSearchTerm(): string { get stringSearchTerm(): string {
// console.log("stringSearchTerm:", this.searchTerm); // If searchTerm is a string, return it directly
if (typeof this.searchTerm === "string") { if (typeof this.searchTerm === "string") {
return this.searchTerm; return this.searchTerm;
// If searchTerm is a Suggestion, return its value and type alias
} else if (this.searchTerm instanceof Suggestion) { } else if (this.searchTerm instanceof Suggestion) {
return this.searchTerm.value + " (" + this.getTypeAlias(this.searchTerm.type) + ")"; return this.searchTerm.value + " (" + this.getTypeAlias(this.searchTerm.type) + ")";
// return this.searchTerm.value + " (" + this.searchTerm.type + ")"; // return this.searchTerm.value + " (" + this.searchTerm.type + ")";
// Default to empty string
} else { } else {
return ""; return "";
} }
@ -109,9 +110,6 @@ export default class SearchViewComponent extends Vue {
return false; return false;
} }
} }
// getKeyName(value: string) {
// return Object.entries(Suggestion).find(([key, val]) => val === value)?.[0];
// }
// Method to get enum key by enum value // Method to get enum key by enum value
getEnumKeyByEnumValue<T extends { [index: string]: string }>(myEnum: T, enumValue: string): keyof T | null { getEnumKeyByEnumValue<T extends { [index: string]: string }>(myEnum: T, enumValue: string): keyof T | null {
@ -124,7 +122,6 @@ export default class SearchViewComponent extends Vue {
beforeMount(): void { beforeMount(): void {
// console.log("beforeMount!"); // console.log("beforeMount!");
// this.rdrAPI = new DatasetService();
// Trigger search based on provided display and type props // Trigger search based on provided display and type props
if (this.display != "" && this.type != undefined) { if (this.display != "" && this.type != undefined) {
const enumKey: "Title" | "Author" | "Subject" | "Doctype" | null = this.getEnumKeyByEnumValue(SearchType, this.type); const enumKey: "Title" | "Author" | "Subject" | "Doctype" | null = this.getEnumKeyByEnumValue(SearchType, this.type);
@ -150,64 +147,30 @@ export default class SearchViewComponent extends Vue {
this.activeFilterCategories = new ActiveFilterCategories(); this.activeFilterCategories = new ActiveFilterCategories();
this.facets = new FacetResults(); this.facets = new FacetResults();
this.searchTerm = suggestion; this.searchTerm = suggestion;
// console.log("ONSEARCH > suggestion: ", suggestion);
// /* Perform faceted search. The method returns an Observable, and the code subscribes to this Observable to handle the response. If the response is successful, it calls the dataHandler method // /* Perform faceted search. The method returns an Observable, and the code subscribes to this Observable to handle the response. If the response is successful, it calls the dataHandler method
// with the Solr response as a parameter. If there is an error, it calls the errorHandler method with the error message as a parameter */ // with the Solr response as a parameter. If there is an error, it calls the errorHandler method with the error message as a parameter */
// DatasetService.facetedSearch(suggestion, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({ // DatasetService.facetedSearchSOLR(suggestion, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({
// next: (res: SolrResponse) => this.dataHandler(res), // next: (res: SolrResponse) => this.dataHandler(res),
// error: (error: string) => this.errorHandler(error), // error: (error: string) => this.errorHandler(error),
// }); // });
DatasetService.facetedSearchOPEN(suggestion, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({ DatasetService.facetedSearch(suggestion, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({
// next: (res: { datasets: Dataset[], highlights: HitHighlight[] }) => this.dataHandlerOpen(res.datasets, res.highlights), next: (res: OpenSearchResponse) => this.dataHandler(res),
next: (res: OpenSearchResponse) => this.dataHandlerOPEN(res),
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
}); });
} }
// Handle the search results // Handle the search results
private dataHandlerOPEN(res: OpenSearchResponse, filterItem?: FacetItem): void { private dataHandler(res: OpenSearchResponse, filterItem?: FacetItem): void {
this.results = res.hits.hits.map(hit => hit._source); this.results = res.hits.hits.map(hit => hit._source);
this.numFound = res.hits.total.value; this.numFound = res.hits.total.value;
// console.log("dataHandlerOPEN (results, numFound):");
// console.log(this.results);
// console.log(this.numFound);
// console.log(res.hits.hits);
// console.log(res.hits.total.value); console.log(res);
// console.log("results:");
// console.log(res);
// for (const key in this.results) {
// if (Object.prototype.hasOwnProperty.call(this.results, key)) {
// const element = this.results[key];
// // console.log(element.abstract[0]);
// // console.log(element.language);
// console.log(element.server_date_published);
// }
// }
this.pagination.total = res.hits.total.value; this.pagination.total = res.hits.total.value;
this.pagination.perPage = 10; this.pagination.perPage = 10;
this.pagination.data = this.results; this.pagination.data = this.results;
this.pagination.lastPage = Math.ceil(this.pagination.total / this.pagination.perPage); this.pagination.lastPage = Math.ceil(this.pagination.total / this.pagination.perPage);
// if (res.aggregations) {
// const facet_fields = res.aggregations;
// let prop: keyof typeof facet_fields;
// for (prop in facet_fields) {
// const facetCategory = facet_fields[prop];
// if (facetCategory.buckets) {
// const facetItems = facetCategory.buckets.map(bucket => new FacetItem(bucket.key, bucket.doc_count));
// this.facets[prop] = facetItems.filter(el => el.count > 0);
// }
// }
// }
if (res.aggregations) { if (res.aggregations) {
const facet_fields = res.aggregations; const facet_fields = res.aggregations;
@ -244,7 +207,7 @@ export default class SearchViewComponent extends Vue {
} }
// // Method to handle search response // // Method to handle search response
// private dataHandler(res: SolrResponse, filterItem?: FacetItem): void { // private dataHandlerSOLR(res: SolrResponse, filterItem?: FacetItem): void {
// // console.log("dataHandlerSOLR (docs, numFound):"); // // console.log("dataHandlerSOLR (docs, numFound):");
// // console.log(res.response.docs); // // console.log(res.response.docs);
// // console.log(res.response.numFound); // // console.log(res.response.numFound);
@ -315,13 +278,13 @@ export default class SearchViewComponent extends Vue {
const start = page * this.pagination.perPage - this.pagination.perPage; const start = page * this.pagination.perPage - this.pagination.perPage;
// // Trigger new search with updated pagination parameters // // Trigger new search with updated pagination parameters
// DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, start.toString()).subscribe( // DatasetService.facetedSearchSOLR(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, start.toString()).subscribe(
// (res: SolrResponse) => this.dataHandler(res), // (res: SolrResponse) => this.dataHandler(res),
// (error: string) => this.errorHandler(error), // (error: string) => this.errorHandler(error),
// ); // );
DatasetService.facetedSearchOPEN(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, start.toString()).subscribe({ DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, start.toString()).subscribe({
next: (res: OpenSearchResponse) => this.dataHandlerOPEN(res), next: (res: OpenSearchResponse) => this.dataHandler(res),
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
}); });
} }
@ -332,37 +295,32 @@ export default class SearchViewComponent extends Vue {
// Reset current page // Reset current page
this.pagination.currentPage = 1; this.pagination.currentPage = 1;
// console.log(facetItem.val);
// console.log(facetItem.category);
// if (!this.activeFilterCategories.hasOwnProperty(facetItem.category)) {
// Check if filter item already exists // Check if filter item already exists
if (!Object.prototype.hasOwnProperty.call(this.activeFilterCategories, facetItem.category)) { if (!Object.prototype.hasOwnProperty.call(this.activeFilterCategories, facetItem.category)) {
this.activeFilterCategories[facetItem.category] = new Array<string>(); this.activeFilterCategories[facetItem.category] = new Array<string>();
// console.log(this.activeFilterCategories);
} }
// if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) {
// Check if filter item is not already applied // Check if filter item is not already applied
if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) { if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) {
// Add filter item to active filter categories // Add filter item to active filter categories
this.activeFilterCategories[facetItem.category].push(facetItem.val); this.activeFilterCategories[facetItem.category].push(facetItem.val);
// Trigger new search with updated filter
// DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe( // DatasetService.facetedSearchSOLR(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe(
// (res: SolrResponse) => this.dataHandler(res, facetItem), // (res: SolrResponse) => this.dataHandler(res, facetItem),
// (error: string) => this.errorHandler(error), // (error: string) => this.errorHandler(error),
// ); // );
// console.log(this.activeFilterCategories);
DatasetService.facetedSearchOPEN(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({ // Trigger new search with updated filter
next: (res: OpenSearchResponse) => this.dataHandlerOPEN(res, facetItem), DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({
next: (res: OpenSearchResponse) => this.dataHandler(res, facetItem),
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
}); });
} }
} }
// // // Method to clear facet category filter // // // Method to clear facet category filter
// onClearFacetCategory(categoryName: string): void { // onClearFacetCategorySOLR(categoryName: string): void {
// console.log("onClearFacetCategory"); // console.log("onClearFacetCategory");
// delete this.activeFilterCategories[categoryName]; // delete this.activeFilterCategories[categoryName];
@ -416,12 +374,12 @@ export default class SearchViewComponent extends Vue {
// } // }
// Method to clear facet category filter // Method to clear facet category filter
onClearFacetCategoryOPEN(categoryName: string): void { onClearFacetCategory(categoryName: string): void {
console.log("onClearFacetCategory"); // console.log("onClearFacetCategory");
delete this.activeFilterCategories[categoryName]; delete this.activeFilterCategories[categoryName];
// Trigger new search with updated filter // Trigger new search with updated filter
DatasetService.facetedSearchOPEN(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({ DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({
next: (res: OpenSearchResponse) => { next: (res: OpenSearchResponse) => {
this.results = res.hits.hits.map(hit => hit._source); this.results = res.hits.hits.map(hit => hit._source);
this.numFound = res.hits.total.value; this.numFound = res.hits.total.value;

View File

@ -105,7 +105,7 @@
<active-facet-category <active-facet-category
v-bind:filterItems="values" v-bind:filterItems="values"
v-bind:categoryName="key" v-bind:categoryName="key"
@clear-facet-category="onClearFacetCategoryOPEN" @clear-facet-category="onClearFacetCategory"
></active-facet-category> ></active-facet-category>
</span> </span>
</div> </div>