import api from "../api/api"; // import { Observable, of } from "rxjs"; import { Observable } from "rxjs"; import { tap, map } from "rxjs/operators"; import { Dataset, DbDataset, Suggestion } from "@/models/dataset"; import { HitHighlight, OpenSearchResponse, SolrResponse } from "@/models/headers"; import { ActiveFilterCategories } from "@/models/solr"; import { VUE_API } from "@/constants"; import { deserialize } from "class-transformer"; class DatasetService { /** * Fetch data from the OpenSearch endpoint with fuzzy search enabled. * This function allows for misspellings in the search term and boosts * the relevance of matches in the title, author, and subject fields. * * @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 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): Observable { public searchTerm(term: string): Observable<{ datasets: Dataset[], highlights: HitHighlight[] }> { const body = { query: { bool: { should: [ { 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"! ], minimum_should_match: 1 } }, size: 10, from: 0, // 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: { language: { terms: { field: "language.keyword" } }, 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 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(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 => ({ datasets: response.hits.hits.map(hit => hit._source), highlights: response.hits.hits.map(hit => hit.highlight) })) ); } // 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 const host = "https://" + solrHost; const path = "/solr/" + solrCore + "/select?"; const base = host + path; //const fields = 'id,server_date_published,abstract_output,title_output,title_additional,author,subject'; // fields we want returned const fields = [ "id", "licence", "server_date_published", "abstract_output", "title_output", "title_additional", "author", "subject", "doctype", ].toString(); const qfFields = "title^3 author^2 subject^1"; const q_params = { "0": "fl=" + fields, q: term + "*", defType: "edismax", qf: qfFields, indent: "on", wt: "json", }; // Make API call to Solr and return the result /** * When a GET request is made to the Solr server using the api.get method, the response received from Solr is an object that includes various details about the search results. * One of the key properties of this response object is docs, 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. */ const stations = api.get(base, q_params).pipe(map((res: SolrResponse) => res.response.docs)); return stations; } /* E.g. Only one facet => Author: Coric, Stjepan (16) 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&fq=author%3A%28%22Coric%2C%20Stjepan%22%29&start=0&sort=server_date_published%20desc&facet=on &json.facet.language=%7B%20type%3A%20%22terms%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%22terms%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 */ /** * 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( suggestion: Suggestion | string, activeFilterCategories: ActiveFilterCategories, solrCore: string, solrHost: string, start?: string, // Starting page ): Observable { // Construct Solr query parameters const host = "https://" + solrHost; const path = "/solr/" + solrCore + "/select?"; const base = host + path; const fields = [ "id", "licence", "server_date_published", "abstract_output", "identifier", "title_output", "title_additional", "author", "subject", "doctype", ].toString(); // Determine search term, query operator, and query fields based on the suggestion type. Depending on whether suggestion is a string or a Suggestion object, it constructs the search term and query fields differently. let term, queryOperator, qfFields; if (typeof suggestion === "string") { // f suggestion is a string, it appends a wildcard (*) for partial matches. term = suggestion + "*"; queryOperator = "or"; qfFields = "title^3 author^2 subject^1"; } else if (suggestion instanceof Suggestion) { // If suggestion is a Suggestion object, it forms a more specific query based on the type and value of the suggestion. term = suggestion.type + ':"' + suggestion.value + '"'; queryOperator = "and"; qfFields = undefined; } // Set default value for start if not provided if (start === undefined) start = "0"; // Construct filter fields based on active filter categories const filterFields = new Array(); if (Object.keys(activeFilterCategories).length > 0) { /* Declare variable prop with a type that is a key of the activeFilterCategories. The 'keyof typeof' activeFilterCategories type represents all possible keys that can exist on the activeFilterCategories --> prop can only be assigned a value that is a key of the activeFilterCategories object */ let prop: keyof typeof activeFilterCategories; for (prop in activeFilterCategories) { const filterItems = activeFilterCategories[prop]; filterItems.forEach(function (value: string) { filterFields.push(prop + ':("' + value + '")'); // e.g. Array [ 'subject:("Vektordaten")', 'author:("GeoSphere Austria, ")' ] }); } } // https://solr.apache.org/guide/8_4/json-request-api.html // Construct Solr query parameters const q_params = { "0": "fl=" + fields, q: term, "q.op": queryOperator, defType: "edismax", qf: qfFields, // df: "title", indent: "on", wt: "json", rows: 10, // fq: ["subject:Steiermark", "language:de"], fq: filterFields, start: start, sort: "server_date_published desc", facet: "on", // "facet.field": "language", "json.facet.language": '{ type: "terms", field: "language" }', "json.facet.subject": '{ type: "terms", field: "subject", limit: -1 }', "json.facet.year": '{ type: "terms", field: "year" }', "json.facet.author": '{ type: "terms", field: "author_facet", limit: -1 }', }; /* E.g. {"0":"fl=id,licence,server_date_published,abstract_output,identifier,title_output,title_additional,author,subject,doctype","q":"*","q.op":"or","defType":"edismax", "qf":"title^3 author^2 subject^1", "indent":"on","wt":"json","rows":10, "fq":["subject:(\"Vektordaten\")","author:(\"GeoSphere Austria, \")"], "start":"0","sort":"server_date_published desc","facet":"on", "json.facet.language":"{ type: \"terms\", field: \"language\" }", "json.facet.subject":"{ type: \"terms\", field: \"subject\", limit: -1 }", "json.facet.year":"{ type: \"terms\", field: \"year\" }", "json.facet.author":"{ type: \"terms\", field: \"author_facet\", limit: -1 }"} */ // console.log(JSON.stringify(q_params)); // Make API call to Solr and return the result const stations = api.get(base, q_params); return stations; } // Method to fetch years public getYears(): Observable { const host = VUE_API; const path = "/api/years"; const base = host + path; const years = api.get(base); return years; } // Method to fetch documents for a specific year public getDocuments(year: string): Observable> { const host = VUE_API; const path = "/api/sitelinks/" + year; const base = host + path; const documents: Observable = api.get>(base); return documents; } // Method to fetch a dataset by its ID public getDataset(id: number): Observable { const host = VUE_API; const path = "/api/dataset/" + id; const apiUrl = host + path; const dataset = api.get(apiUrl).pipe(map((res) => this.prepareDataset(res))); return dataset; } // Method to fetch a dataset by its DOI public getDatasetByDoi(doi: string): Observable { const host = VUE_API; const path = "/api/dataset/10.24341/tethys." + doi; const apiUrl = host + path; const dataset = api.get(apiUrl).pipe(map((res) => this.prepareDataset(res))); return dataset; } // Method to prepare dataset object private prepareDataset(datasetObj: DbDataset): DbDataset { const dataset = deserialize(DbDataset, JSON.stringify(datasetObj)); dataset.url = document.documentURI; return dataset; } } export default new DatasetService();