From 9b3f7eff5fe55ffb0288a6546960901b08bbd1b7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:51:45 +0200 Subject: [PATCH] Task/improve search log output for unsupported queries (#7480) * Improve search log output for unsupported queries * Update changelog --- CHANGELOG.md | 2 ++ .../data-providers/ghostfolio/ghostfolio.service.ts | 13 +++++++------ .../services/data-provider/data-provider.service.ts | 7 +++++-- .../yahoo-finance/yahoo-finance.service.ts | 6 +++++- libs/common/src/lib/config.ts | 2 ++ libs/common/src/lib/helper.ts | 5 +++++ libs/ui/src/lib/assistant/assistant.component.ts | 9 +++++---- .../symbol-autocomplete.component.ts | 8 ++++++-- 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 421a1bd6c..83ff99150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the activity count to the delete menu item of the activities table - Added the activity count to the deletion confirmation dialog of the activities table - Improved the style of the type filter in the activities table component (experimental) +- Improved the search functionality by trimming the query +- Improved the log output in the search functionality of the _Yahoo Finance_ service for unsupported queries - Improved the performance of the property service by caching the properties in memory - Improved the language localization for German (`de`) diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts index 31d20b99e..b858688c2 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -17,7 +17,10 @@ import { DERIVED_CURRENCIES } from '@ghostfolio/common/config'; import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config'; -import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; +import { + getAssetProfileIdentifier, + isValidSearchQuery +} from '@ghostfolio/common/helper'; import { DataProviderGhostfolioAssetProfileResponse, DataProviderHistoricalResponse, @@ -344,7 +347,9 @@ export class GhostfolioService { }: GetSearchParams): Promise { const results: LookupResponse = { items: [] }; - if (!query) { + query = query?.trim(); + + if (!isValidSearchQuery(query)) { return results; } @@ -352,10 +357,6 @@ export class GhostfolioService { let lookupItems: LookupItem[] = []; const promises: Promise<{ items: LookupItem[] }>[] = []; - if (query?.length < 2) { - return { items: lookupItems }; - } - for (const dataProviderService of this.getDataProviderServices()) { promises.push( dataProviderService.search({ diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index e8d5f5030..4c273f2da 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -20,7 +20,8 @@ import { getCurrencyFromSymbol, getStartOfUtcDate, isCurrency, - isDerivedCurrency + isDerivedCurrency, + isValidSearchQuery } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, @@ -838,7 +839,9 @@ export class DataProviderService implements OnModuleInit { let lookupItems: LookupItem[] = []; const promises: Promise[] = []; - if (query?.length < 2) { + query = query?.trim(); + + if (!isValidSearchQuery(query)) { return { items: lookupItems }; } diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index 52fb5d5a8..364354f6b 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -330,7 +330,11 @@ export class YahooFinanceService implements DataProviderInterface { }); } } catch (error) { - this.logger.error(error); + if (error?.name === 'BadRequestError') { + this.logger.warn(`Could not search for "${query}": ${error.message}`); + } else { + this.logger.error(error); + } } return { items }; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 890c29fbb..08fb99e28 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -288,6 +288,8 @@ export const REPLACE_NAME_PARTS = [ 'Xtrackers (IE) Plc -' ]; +export const SEARCH_QUERY_MINIMUM_LENGTH = 2; + export const SECTORS = [ 'Basic Materials', 'Communication Services', diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index b2a3ab419..98f097b44 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -41,6 +41,7 @@ import { DERIVED_CURRENCIES, ghostfolioFearAndGreedIndexSymbolCryptocurrencies, ghostfolioFearAndGreedIndexSymbolStocks, + SEARCH_QUERY_MINIMUM_LENGTH, TAG_ID_EXCLUDE_FROM_ANALYSIS } from './config'; import { @@ -509,6 +510,10 @@ export function isRootCurrency(aCurrency: string) { }); } +export function isValidSearchQuery(aQuery: string) { + return aQuery?.trim().length >= SEARCH_QUERY_MINIMUM_LENGTH; +} + export function parseDate(date: string): Date | undefined { if (!date) { return undefined; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 471420937..9ef6c9f1c 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -202,6 +202,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.searchFormControl.valueChanges .pipe( map((searchTerm) => { + return searchTerm?.trim(); + }), + debounceTime(300), + distinctUntilChanged(), + tap(() => { this.isLoading = { accounts: true, assetProfiles: true, @@ -216,11 +221,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }; this.changeDetectorRef.markForCheck(); - - return searchTerm?.trim(); }), - debounceTime(300), - distinctUntilChanged(), switchMap((searchTerm) => { const results = { accounts: [], diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index cab911ef2..704c4ed44 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -41,6 +41,7 @@ import { debounceTime, distinctUntilChanged, filter, + map, switchMap } from 'rxjs/operators'; @@ -128,6 +129,9 @@ export class GfSymbolAutocompleteComponent this.control.valueChanges .pipe( + map((query) => { + return isString(query) ? query.trim() : query; + }), filter((query) => { if (query?.length === 0) { this.showDefaultOptions(); @@ -137,13 +141,13 @@ export class GfSymbolAutocompleteComponent return isString(query); }), + debounceTime(400), + distinctUntilChanged(), tap(() => { this.isLoading = true; this.changeDetectorRef.markForCheck(); }), - debounceTime(400), - distinctUntilChanged(), takeUntilDestroyed(this.destroyRef), switchMap((query: string) => { return this.dataService.fetchSymbols({