From a70e454cbce3864ace9f4fc83a4372d7de853d3f Mon Sep 17 00:00:00 2001 From: frankporras Date: Tue, 11 Jun 2024 14:38:50 +0200 Subject: [PATCH] OpenSearch progress. Stetic changes in result list. Faceted search not started --- .env.example | 6 +- src/components/vs-input/vs-input.ts | 49 +----- src/components/vs-input/vs-input.vue | 2 +- src/services/dataset.service.ts | 157 +++--------------- .../dataset-detail.component.ts | 2 + .../search-view/search-view-component.ts | 4 + 6 files changed, 41 insertions(+), 179 deletions(-) diff --git a/.env.example b/.env.example index ea6aeaa..2f97d5d 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ APP_URL=//tethys.at VUE_API=//www.tethys.at -SOLR_HOST=tethys.at -SOLR_CORE=rdr_data \ No newline at end of file +# SOLR_HOST=tethys.at +# SOLR_CORE=rdr_data +OPEN_HOST=192.168.21.18 +OPEN_CORE=tethys-records \ No newline at end of file diff --git a/src/components/vs-input/vs-input.ts b/src/components/vs-input/vs-input.ts index 7f685a0..68e1cbb 100644 --- a/src/components/vs-input/vs-input.ts +++ b/src/components/vs-input/vs-input.ts @@ -37,15 +37,13 @@ export default class VsInput extends Vue { private loading = false; // Loading state indicator private selectedIndex = -1; // Index of the currently selected suggestion - // private selectedDisplay = ""; + private solr: SolrSettings = { core: SOLR_CORE, //"rdr_data", // SOLR.core; host: SOLR_HOST, //"tethys.at", - // core: "test_data", // SOLR.core; - // host: "repository.geologie.ac.at", }; - private open: OpenSettings = { + private openSearch: OpenSettings = { core: OPEN_CORE, //"rdr_data", // SOLR.core; host: OPEN_HOST, //"tethys.at", // core: "test_data", // SOLR.core; @@ -126,7 +124,7 @@ export default class VsInput extends Vue { if (highlight.author && highlight.author.length > 0) { const highlightedAuthor = highlight.author.join(" "); const datasetAuthor = this.find(dataset.author, this.display.toLowerCase()); - const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.highlight === highlightedAuthor && suggestion.type == SearchType.Author); + const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedAuthor.toLowerCase() && suggestion.type == SearchType.Author); if (!hasAuthorSuggestion) { const suggestion = new Suggestion(datasetAuthor, highlightedAuthor, SearchType.Author); suggestions.push(suggestion); @@ -135,48 +133,13 @@ export default class VsInput extends Vue { if (highlight.subjects && highlight.subjects.length > 0) { const highlightedSubject = highlight.subjects.join(" "); const datasetSubject = this.find(dataset.subjects, this.display.toLowerCase()); - const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.highlight === highlightedSubject && suggestion.type == SearchType.Subject); + const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedSubject.toLowerCase() && suggestion.type == SearchType.Subject); if (!hasSubjectSuggestion) { const suggestion = new Suggestion(datasetSubject, highlightedSubject, SearchType.Subject); suggestions.push(suggestion); } } - // // Checks if a suggestion with the same title and type already exists in the suggestions array. If not, it creates a new Suggestion object and adds it to the suggestions array. - // if (highlight.title && highlight.title.length > 0) { - // /** This line checks if the highlight object has a title property and if that property is an array with at least one element. - // * The highlight object contains highlighted fragments of the search term in various fields (e.g., title, author, subjects) as returned by the OpenSearch API. - // * This check ensures that we only process results that have highlighted titles. */ - // const title = highlight.title.join(" "); - // /** - // * The highlight.title property is an array of strings, where each string is a highlighted fragment of the title. join(" ") combines these fragments into a single string with spaces between them. - // * This step constructs a full highlighted title from the individual fragments. - // * OpenSearch can return multiple fragments of a field (like the title) in its response, especially when the field contains multiple terms that match the search query. - // * This can happen because OpenSearch's highlighting feature is designed to provide context around each match within the field, which can result in multiple highlighted fragments. - // */ - // const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title); - // if (!hasTitleSuggestion) { - // const suggestion = new Suggestion(title, SearchType.Title); - // suggestions.push(suggestion); - // } - // } - // if (highlight.author && highlight.author.length > 0) { - // const author = highlight.author.join(" "); - // const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author); - // if (!hasAuthorSuggestion) { - // const suggestion = new Suggestion(author, SearchType.Author); - // suggestions.push(suggestion); - // } - // } - // if (highlight.subjects && highlight.subjects.length > 0) { - // const subject = highlight.subjects.join(" "); - // const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject); - // if (!hasSubjectSuggestion) { - // const suggestion = new Suggestion(subject, SearchType.Subject); - // suggestions.push(suggestion); - // } - // } - // ORIGINAL SOLR =================================================================================================== // if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) { // const title = dataset.title_output; @@ -238,6 +201,7 @@ export default class VsInput extends Vue { and emits a search-change event with the current value of display as the argument. */ @Emit("search-change") search(): string { + console.log("search"); this.results = []; // this.$emit("search", this.display) this.value = this.display; //(obj["title_output"]) ? obj["title_output"] : obj.id @@ -260,6 +224,7 @@ export default class VsInput extends Vue { // Perform the search request private resourceSearch() { + console.log("resourceSearch"); if (!this.display) { this.results = []; return; @@ -273,7 +238,7 @@ export default class VsInput extends Vue { private request(): void { console.log("request()"); // DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({ - DatasetService.searchTerm(this.display).subscribe({ + DatasetService.searchTerm(this.display, this.openSearch.core, this.openSearch.host).subscribe({ // next: (res: Dataset[]) => this.dataHandler(res), next: (res: { datasets: Dataset[], highlights: HitHighlight[] }) => this.dataHandler(res.datasets, res.highlights), error: (error: string) => this.errorHandler(error), diff --git a/src/components/vs-input/vs-input.vue b/src/components/vs-input/vs-input.vue index be7e855..e06d528 100644 --- a/src/components/vs-input/vs-input.vue +++ b/src/components/vs-input/vs-input.vue @@ -149,7 +149,7 @@ input { .autocomplete-result-item { list-style: none; text-align: left; - /* padding: 7px 10px; */ + padding: 0px 0px 0px 5px; // top,right,bottom,left cursor: pointer; } diff --git a/src/services/dataset.service.ts b/src/services/dataset.service.ts index 3d7e687..0b201e8 100644 --- a/src/services/dataset.service.ts +++ b/src/services/dataset.service.ts @@ -16,131 +16,7 @@ class DatasetService { * * @param {string} searchTerm - The search term to query. */ - // async fetchDataFromOpenSearch(searchTerm: string): Promise { - // // Define the OpenSearch endpoint URL - // const url = "http://opensearch.geoinformation.dev/tethys-records/_search"; - - // // Set the headers for the POST request - // const headers = { - // "Content-Type": "application/json", - // }; - - // // Construct the body of the POST request - // const body = { - // query: { - // bool: { - // // The `should` clause specifies that at least one of these conditions must match - // should: [ - // { - // // Match the search term in the title field with fuzziness enabled and a boost of 3 - // match: { - // title: { - // query: searchTerm, - // fuzziness: "AUTO", // Enable fuzzy search - // 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: { - // author: { - // query: searchTerm, - // fuzziness: "AUTO", // Enable fuzzy search - // 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: { - // subject: { - // query: searchTerm, - // fuzziness: "AUTO", // Enable fuzzy search - // boost: 1 // Boosting the relevance of subject matches - // } - // } - // }, - // { - // // Match the search term in the title field with a wildcard - // wildcard: { - // title: { - // value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm - // boost: 3 // Boosting the relevance of title matches - // } - // } - // }, - // { - // // Match the search term in the author field with a wildcard - // wildcard: { - // author: { - // value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm - // boost: 2 // Boosting the relevance of author matches - // } - // } - // }, - // { - // // Match the search term in the subject field with a wildcard - // wildcard: { - // subject: { - // value: `${searchTerm}*`, // Wildcard search for terms starting with searchTerm - // boost: 1 // Boosting the relevance of subject matches - // } - // } - // } - // ], - // // Ensure that at least one of the `should` clauses must match - // minimum_should_match: 1 - // } - // }, - // // Limit the number of search results to 10 - // size: 10, - // // Start from the first result (pagination) - // from: 0, - // // Sort the results by the `server_date_published` field in descending order - // sort: [ - // { server_date_published: { order: "desc" } } - // ], - // // Aggregations to provide facets for the `language` and `subject` fields - // aggs: { - // language: { - // terms: { - // field: "language.keyword" // Aggregate by the exact values of the `language` field - // } - // }, - // subject: { - // terms: { - // field: "subjects.keyword", // Aggregate by the exact values of the `subjects` field - // size: 10 // Limit the number of aggregation buckets to 10 - // } - // } - // } - // }; - - // try { - // // Send the POST request to the OpenSearch endpoint - // const response = await fetch(url, { - // method: "POST", - // headers: headers, - // body: JSON.stringify(body), - // }); - - // // Check if the response is not successful - // if (!response.ok) { - // throw new Error(`Failed to fetch data from ${url}, status: ${response.status}`); - // } - - // // Parse the response JSON - // const data = await response.json(); - // // Log the data from OpenSearch - // console.log("Data from OpenSearch:", data); - // console.log("Hits:", data.hits.total.value); - // } catch (error) { - // // Log any errors that occur during the fetch process - // 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 &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 @@ -148,10 +24,19 @@ class DatasetService { */ // private openSearchUrl = "http://opensearch.geoinformation.dev/tethys-records/_search"; - private openSearchUrl = "http://192.168.21.18/tethys-records/_search"; + // private openSearchUrl = "http://192.168.21.18/tethys-records/_search"; // public searchTerm(term: string): Observable { - public searchTerm(term: string): Observable<{ datasets: Dataset[], highlights: HitHighlight[] }> { + public searchTerm(term: string, openCore: string, openHost: string): Observable<{ datasets: Dataset[], highlights: HitHighlight[] }> { + // OpenSearch endpoint + const host = "https://" + openHost; // When using geoinformation.dev + // const host = "http://" + openHost; // When using local OpenSearch dev endpoint + 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 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 body = { query: { bool: { @@ -159,9 +44,9 @@ class DatasetService { { match: { title: { query: term, fuzziness: "AUTO", boost: 3 } } }, { match: { author: { query: term, fuzziness: "AUTO", boost: 2 } } }, { match: { subjects: { query: term, fuzziness: "AUTO", boost: 1 } } }, // In SOLR is "subject"! - { wildcard: { title: { value: `${term}*`, boost: 3 } } }, - { wildcard: { author: { value: `${term}*`, boost: 2 } } }, - { wildcard: { subjects: { value: `${term}*`, boost: 1 } } } // In SOLR is "subject"! + { wildcard: { title: { value: `${lowercaseTerm}*`, boost: 3 } } }, + { wildcard: { author: { value: `${lowercaseTerm}*`, boost: 2 } } }, + { wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } } // In SOLR is "subject"! ], minimum_should_match: 1 } @@ -190,7 +75,7 @@ class DatasetService { * 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(this.openSearchUrl, body).pipe( + return api.post(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 @@ -206,9 +91,7 @@ class DatasetService { // For the autocomplete search. Method to perform a search based on a term public searchTerm_SOLR(term: string, solrCore: string, solrHost: string): Observable { - // Calling the test method for - // this.fetchDataFromOpenSearch(term); - // solr endpoint + // SOLR endpoint const host = "https://" + solrHost; const path = "/solr/" + solrCore + "/select?"; const base = host + path; @@ -268,6 +151,12 @@ class DatasetService { solrHost: string, start?: string, // Starting page ): Observable { + // console.log("face:", suggestion); + // console.log(activeFilterCategories); + // console.log(solrCore); + // console.log(solrHost); + // console.log(start); + // Construct Solr query parameters const host = "https://" + solrHost; const path = "/solr/" + solrCore + "/select?"; diff --git a/src/views/dataset-detail.component/dataset-detail.component.ts b/src/views/dataset-detail.component/dataset-detail.component.ts index af8a4e9..8a3d435 100644 --- a/src/views/dataset-detail.component/dataset-detail.component.ts +++ b/src/views/dataset-detail.component/dataset-detail.component.ts @@ -59,6 +59,8 @@ export default class DatasetDetailComponent extends Vue { } onSearch(suggestion: Suggestion | string): void { + console.log("onSearch"); + const host = window.location.host; const parts = host.split("."); if (parts[0] === "doi") { diff --git a/src/views/search-view/search-view-component.ts b/src/views/search-view/search-view-component.ts index 68742da..d87fa9c 100644 --- a/src/views/search-view/search-view-component.ts +++ b/src/views/search-view/search-view-component.ts @@ -122,11 +122,15 @@ export default class SearchViewComponent extends Vue { // Method to trigger a search onSearch(suggestion: Suggestion | string): void { + console.log("ONSEARCH"); + // Reset active filter categories and facet results this.activeFilterCategories = new ActiveFilterCategories(); this.facets = new FacetResults(); this.searchTerm = suggestion; + console.log("This.searchterm: ", this.searchTerm); + /* 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 */ DatasetService.facetedSearch(suggestion, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({