From 67a64074b4e8d2b9e82c2ef1832be8359dcaaf5e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:24:55 +0100 Subject: [PATCH] Task/improve data source filter in market data table (#8327) * Improve data source filter * Update changelog --- CHANGELOG.md | 1 + .../admin-market-data.component.ts | 52 +++++++++++++++---- libs/ui/src/lib/i18n.ts | 1 + 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ae035f81..831a699785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the data source filter in the historical market data table of the admin control panel to only list the data providers in use - Restricted the health check endpoint of the AI service (`GET api/v1/health/ai`) to administrators - Increased the timeout in the health check endpoint of the AI service to 30 seconds - Deprecated the `api/v1/access` endpoints in favor of the `api/v1/accesses` endpoints diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index d1ea9d97eb..e1a17fd75f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -11,7 +11,7 @@ import { import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import type { SymbolProfile } from '@ghostfolio/prisma/browser'; -import { AssetSubClass, DataSource } from '@ghostfolio/prisma/enums'; +import { AssetSubClass } from '@ghostfolio/prisma/enums'; import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { GfFabComponent } from '@ghostfolio/ui/fab'; import { translate } from '@ghostfolio/ui/i18n'; @@ -61,6 +61,7 @@ import { ellipsisVertical, trashOutline } from 'ionicons/icons'; +import { isEqual } from 'lodash-es'; import ms from 'ms'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; @@ -95,7 +96,7 @@ import { AdminMarketDataService } from './admin-market-data.service'; export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { protected readonly adminMarketDataService = inject(AdminMarketDataService); - protected readonly allFilters: Filter[] = [ + protected allFilters: Filter[] = [ ...Object.keys(AssetSubClass) .filter((assetSubClass) => { return assetSubClass !== 'CASH'; @@ -107,13 +108,6 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { type: 'ASSET_SUB_CLASS' as Filter['type'] }; }), - ...Object.keys(DataSource).map((dataSource) => { - return { - id: dataSource.toString(), - label: dataSource, - type: 'DATA_SOURCE' as Filter['type'] - }; - }), { id: 'BENCHMARKS', label: $localize`Benchmarks`, @@ -202,6 +196,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { this.adminMarketDataService.refresh$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { + this.loadDataSourceFilters(); this.reloadData(); }); @@ -214,7 +209,10 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { }); this.filters$ - .pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) + .pipe( + distinctUntilChanged(isEqual), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((filters) => { this.activeFilters = filters; @@ -250,6 +248,8 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { const { benchmarks } = this.dataService.fetchInfo(); this.benchmarks = benchmarks; + + this.loadDataSourceFilters(); } protected onChangePage(page: PageEvent) { @@ -268,6 +268,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { .deleteAssetProfile({ dataSource, symbol }) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { + this.loadDataSourceFilters(); this.reloadData(); }); } @@ -281,6 +282,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { ) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { + this.loadDataSourceFilters(); this.reloadData(); }); } @@ -382,6 +384,36 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { }); } + private loadDataSourceFilters() { + this.adminService + .fetchAdminData() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(({ dataProviders }) => { + this.allFilters = [ + ...this.allFilters.filter(({ type }) => { + return type !== 'DATA_SOURCE'; + }), + ...dataProviders.flatMap( + ({ assetProfileCount, dataSource, name }) => { + if (assetProfileCount === 0 || !dataSource) { + return []; + } + + return [ + { + id: dataSource, + label: name ?? translate(dataSource), + type: 'DATA_SOURCE' as Filter['type'] + } + ]; + } + ) + ]; + + this.changeDetectorRef.markForCheck(); + }); + } + private notifyDataGatheringHasBeenStarted() { this.snackBar.open( '✅ ' + $localize`Data gathering has been started.`, diff --git a/libs/ui/src/lib/i18n.ts b/libs/ui/src/lib/i18n.ts index 5dcf17b2b3..6dbf193367 100644 --- a/libs/ui/src/lib/i18n.ts +++ b/libs/ui/src/lib/i18n.ts @@ -27,6 +27,7 @@ const locales = { HIGHER_RISK: $localize`Higher Risk`, IMPORT_ACTIVITY_ERROR_IS_DUPLICATE: $localize`This activity already exists.`, LOWER_RISK: $localize`Lower Risk`, + MANUAL: $localize`Manual`, MONTH: $localize`Month`, MONTHS: $localize`Months`, OTHER: $localize`Other`,