diff --git a/src/App.vue b/src/App.vue index 667cd84..9b042da 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,7 +10,7 @@ - + { // solr endpoint const host = "https://" + solrHost; @@ -48,13 +48,14 @@ class DatasetService { wt: "json", }; + // Make API call to Solr and return the result const stations = api.get(base, q_params).pipe(map((res: SolrResponse) => res.response.docs)); return stations; } - /* Only one facet => Author: Coric, Stjepan (16) + /* 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 @@ -62,6 +63,7 @@ class DatasetService { &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 */ + // Method to perform a faceted search public facetedSearch( suggestion: Suggestion | string, activeFilterCategories: ActiveFilterCategories, @@ -69,6 +71,7 @@ class DatasetService { solrHost: string, start?: string, // Starting page ): Observable { + // Construct Solr query parameters const host = "https://" + solrHost; const path = "/solr/" + solrCore + "/select?"; const base = host + path; @@ -86,6 +89,7 @@ class DatasetService { "doctype", ].toString(); + // Determine search term, query operator, and query fields based on the suggestion type let term, queryOperator, qfFields; if (typeof suggestion === "string") { term = suggestion + "*"; @@ -97,10 +101,11 @@ class DatasetService { 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 */ @@ -109,11 +114,13 @@ class DatasetService { 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, @@ -135,12 +142,26 @@ class DatasetService { "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"; @@ -150,6 +171,7 @@ class DatasetService { return years; } + // Method to fetch documents for a specific year public getDocuments(year: string): Observable> { const host = VUE_API; const path = "/api/sitelinks/" + year; @@ -159,6 +181,7 @@ class DatasetService { return documents; } + // Method to fetch a dataset by its ID public getDataset(id: number): Observable { const host = VUE_API; const path = "/api/dataset/" + id; @@ -168,6 +191,7 @@ class DatasetService { 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; @@ -177,6 +201,7 @@ class DatasetService { return dataset; } + // Method to prepare dataset object private prepareDataset(datasetObj: DbDataset): DbDataset { const dataset = deserialize(DbDataset, JSON.stringify(datasetObj)); dataset.url = document.documentURI; diff --git a/src/views/search-view/search-view-component.ts b/src/views/search-view/search-view-component.ts index 53f947d..40475c8 100644 --- a/src/views/search-view/search-view-component.ts +++ b/src/views/search-view/search-view-component.ts @@ -13,6 +13,7 @@ import { SOLR_HOST, SOLR_CORE } from "@/constants"; import { IPagination } from "@/models/pagination"; import PaginationComponent from "@/components/PaginationComponent.vue"; +// Decorate the component and define its name and components @Component({ name: "SearchViewComponent", components: { @@ -23,6 +24,8 @@ import PaginationComponent from "@/components/PaginationComponent.vue"; PaginationComponent, }, }) + +// Define the SearchViewComponent class export default class SearchViewComponent extends Vue { @Prop() display!: string; @@ -53,6 +56,7 @@ export default class SearchViewComponent extends Vue { private error = ""; + // Computed property to get search term as string get stringSearchTerm(): string { if (typeof this.searchTerm === "string") { return this.searchTerm; @@ -63,6 +67,7 @@ export default class SearchViewComponent extends Vue { } } + // Method to check if a search term is present hasSearchTerm(): boolean { if (typeof this.searchTerm === "string" && this.searchTerm !== "") { return true; @@ -75,14 +80,18 @@ export default class SearchViewComponent extends Vue { // getKeyName(value: string) { // return Object.entries(Suggestion).find(([key, val]) => val === value)?.[0]; // } + + // Method to get enum key by enum value getEnumKeyByEnumValue(myEnum: T, enumValue: string): keyof T | null { const keys = Object.keys(myEnum).filter((x) => myEnum[x] == enumValue); return keys.length > 0 ? keys[0] : null; // return keys[0]; } + // Lifecycle hook: executed before the component is mounted beforeMount(): void { // this.rdrAPI = new DatasetService(); + // Trigger search based on provided display and type props if (this.display != "" && this.type != undefined) { const enumKey: "Title" | "Author" | "Subject" | null = this.getEnumKeyByEnumValue(SearchType, this.type); if (enumKey) { @@ -98,12 +107,14 @@ export default class SearchViewComponent extends Vue { } } + // Method to trigger a search onSearch(suggestion: Suggestion | string): void { + // Reset active filter categories and facet results this.activeFilterCategories = new ActiveFilterCategories(); this.facets = new FacetResults(); this.searchTerm = suggestion; - /* 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 */ DatasetService.facetedSearch(suggestion, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({ next: (res: SolrResponse) => this.dataHandler(res), @@ -111,12 +122,13 @@ export default class SearchViewComponent extends Vue { }); } + // Method to handle search response private dataHandler(res: SolrResponse, filterItem?: FacetItem): void { - + // Update results this.results = res.response.docs; this.numFound = res.response.numFound; - // pagination + // Update pagination this.pagination["total"] = res.response.numFound; this.pagination["perPage"] = res.responseHeader.params.rows as number; this.pagination["data"] = res.response.docs; @@ -124,8 +136,11 @@ export default class SearchViewComponent extends Vue { const facet_fields: FacetFields = res.facets; + /* This code declares a variable prop with a type of keys of the facet_fields object. The keyof typeof facet_fields type represents the keys of the facet_fields object. + This means that prop can only hold values that are keys of the facet_fields object. */ let prop: keyof typeof facet_fields; + // Iterate through facet fields for (prop in facet_fields) { const facetCategory = facet_fields[prop]; if (facetCategory.buckets) { @@ -133,55 +148,70 @@ export default class SearchViewComponent extends Vue { let facetValues = facetItems.map((facetItem) => { let rObj: FacetItem; + // Check if current facet item matches filter item if (filterItem?.val == facetItem.val) { rObj = filterItem; } else if (this.facets[prop]?.some((e) => e.val === facetItem.val)) { // console.log(facetValue + " is included") + // Update existing facet item with new count const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val); // console.log(indexOfFacetValue); rObj = this.facets[prop][indexOfFacetValue]; rObj.count = facetItem.count; // rObj = new FacetItem(val, count); } else { + // Create new facet item rObj = new FacetItem(facetItem.val, facetItem.count); } return rObj; }); + // Filter out null values and values with count <= 0 facetValues = facetValues.filter(function (el) { return el != null && el.count > 0; }); // this.facets[prop] = facetCategory; + // Update facet values this.facets[prop] = facetValues; } } } + // Method to handle search errors private errorHandler(err: string): void { this.error = err; } + // Method to handle pagination onMenuClick(page: number) { this.pagination.currentPage = page; const start = page * this.pagination.perPage - this.pagination.perPage; + // Trigger new search with updated pagination parameters DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, start.toString()).subscribe( (res: SolrResponse) => this.dataHandler(res), (error: string) => this.errorHandler(error), ); } + // Method to handle facet filtering onFilter(facetItem: FacetItem): void { + // Reset current page this.pagination.currentPage = 1; // console.log(facetItem.val); // if (!this.activeFilterCategories.hasOwnProperty(facetItem.category)) { + + // Check if filter item already exists if (!Object.prototype.hasOwnProperty.call(this.activeFilterCategories, facetItem.category)) { this.activeFilterCategories[facetItem.category] = new Array(); } // if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) { - if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) { - this.activeFilterCategories[facetItem.category].push(facetItem.val); + // Check if filter item is not already applied + if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) { + // Add filter item to active filter categories + 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( (res: SolrResponse) => this.dataHandler(res, facetItem), (error: string) => this.errorHandler(error), @@ -189,9 +219,11 @@ export default class SearchViewComponent extends Vue { } } + // Method to clear facet category filter onClearFacetCategory(categoryName: string): void { delete this.activeFilterCategories[categoryName]; + // Trigger new search with updated filter DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({ next: (res: SolrResponse) => { this.results = res.response.docs; @@ -214,16 +246,18 @@ export default class SearchViewComponent extends Vue { let rObj: FacetItem; if (this.facets[prop]?.some((e) => e.val === facetItem.val)) { // console.log(facetValue + " is included") + // Update existing facet item with new count const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val); // console.log(indexOfFacetValue); rObj = this.facets[prop][indexOfFacetValue]; rObj.count = facetItem.count; // rObj = new FacetItem(val, count); - //if facet ccategory is reactivated category, deactivate all filter items + // if facet ccategory is reactivated category, deactivate all filter items if (prop == categoryName) { rObj.active = false; } } else { + // Create new facet item rObj = new FacetItem(facetItem.val, facetItem.count); } return rObj;