From 539d3ff754b9e552ded6a7f1a19fe214e3eaf678 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Aug 2022 20:53:50 +0200 Subject: [PATCH] Feature/add asset sub class filter (#1188) * Add asset sub class filter * Update changelog --- CHANGELOG.md | 10 +++ apps/api/src/app/admin/admin.controller.ts | 21 +++++- apps/api/src/app/admin/admin.service.ts | 27 ++++++-- .../admin-market-data.component.ts | 68 +++++++++++++------ .../admin-market-data/admin-market-data.html | 10 +++ .../admin-market-data.module.ts | 2 + .../assset-profile-dialog.module.ts | 2 +- .../allocations/allocations-page.component.ts | 5 +- apps/client/src/app/services/data.service.ts | 28 +++++++- apps/client/src/locales/messages.de.xlf | 40 +++++++---- apps/client/src/locales/messages.xlf | 38 +++++++---- .../src/lib/interfaces/filter.interface.ts | 2 +- 12 files changed, 196 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e81260549..2d5251fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added a filter by asset sub class for the asset profiles in the admin control + +### Changed + +- Improved the language localization for German (`de`) + ## 1.182.0 - 23.08.2022 ### Changed diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index b13ba5b5e..e7209fa01 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -8,7 +8,8 @@ import { import { AdminData, AdminMarketData, - AdminMarketDataDetails + AdminMarketDataDetails, + Filter } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -22,6 +23,7 @@ import { Param, Post, Put, + Query, UseGuards } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; @@ -226,7 +228,9 @@ export class AdminController { @Get('market-data') @UseGuards(AuthGuard('jwt')) - public async getMarketData(): Promise { + public async getMarketData( + @Query('assetSubClasses') filterByAssetSubClasses?: string + ): Promise { if ( !hasPermission( this.request.user.permissions, @@ -239,7 +243,18 @@ export class AdminController { ); } - return this.adminService.getMarketData(); + const assetSubClasses = filterByAssetSubClasses?.split(',') ?? []; + + const filters: Filter[] = [ + ...assetSubClasses.map((assetSubClass) => { + return { + id: assetSubClass, + type: 'ASSET_SUB_CLASS' + }; + }) + ]; + + return this.adminService.getMarketData(filters); } @Get('market-data/:dataSource/:symbol') diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index cbbcda7e6..42ee800ba 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -11,11 +11,13 @@ import { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; -import { Property } from '@prisma/client'; +import { AssetSubClass, Prisma, Property } from '@prisma/client'; import { differenceInDays } from 'date-fns'; +import { groupBy } from 'lodash'; @Injectable() export class AdminService { @@ -63,14 +65,27 @@ export class AdminService { }; } - public async getMarketData(): Promise { + public async getMarketData(filters?: Filter[]): Promise { + const where: Prisma.SymbolProfileWhereInput = {}; + + const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy( + filters, + (filter) => { + return filter.type; + } + ); + const marketData = await this.prismaService.marketData.groupBy({ _count: true, by: ['dataSource', 'symbol'] }); - const currencyPairsToGather: AdminMarketDataItem[] = - this.exchangeRateDataService + let currencyPairsToGather: AdminMarketDataItem[] = []; + + if (filtersByAssetSubClass) { + where.assetSubClass = AssetSubClass[filtersByAssetSubClass[0].id]; + } else { + currencyPairsToGather = this.exchangeRateDataService .getCurrencyPairs() .map(({ dataSource, symbol }) => { const marketDataItemCount = @@ -89,9 +104,11 @@ export class AdminService { sectorsCount: 0 }; }); + } const symbolProfilesToGather: AdminMarketDataItem[] = ( await this.prismaService.symbolProfile.findMany({ + where, orderBy: [{ symbol: 'asc' }], select: { _count: { @@ -100,7 +117,6 @@ export class AdminService { assetClass: true, assetSubClass: true, countries: true, - sectors: true, dataSource: true, Order: { orderBy: [{ date: 'asc' }], @@ -108,6 +124,7 @@ export class AdminService { take: 1 }, scraperConfiguration: true, + sectors: true, symbol: true } }) 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 fdc8b544e..34e6c91ce 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 @@ -14,13 +14,14 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DATE_FORMAT, getDateFormatString } from '@ghostfolio/common/helper'; -import { UniqueAsset, User } from '@ghostfolio/common/interfaces'; +import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; -import { DataSource } from '@prisma/client'; +import { AssetSubClass, DataSource } from '@prisma/client'; import { format, parseISO } from 'date-fns'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; + import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component'; import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces'; @@ -33,9 +34,27 @@ import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/inte export class AdminMarketDataComponent implements OnDestroy, OnInit { @ViewChild(MatSort) sort: MatSort; + public activeFilters: Filter[] = []; + public allFilters: Filter[] = [ + AssetSubClass.BOND, + AssetSubClass.COMMODITY, + AssetSubClass.CRYPTOCURRENCY, + AssetSubClass.ETF, + AssetSubClass.MUTUALFUND, + AssetSubClass.PRECIOUS_METAL, + AssetSubClass.PRIVATE_EQUITY, + AssetSubClass.STOCK + ].map((id) => { + return { + id, + label: id, + type: 'ASSET_SUB_CLASS' + }; + }); public currentDataSource: DataSource; public currentSymbol: string; - public dataSource: MatTableDataSource = new MatTableDataSource(); + public dataSource: MatTableDataSource = + new MatTableDataSource(); public defaultDateFormat: string; public deviceType: string; public displayedColumns = [ @@ -50,7 +69,9 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { 'sectorsCount', 'actions' ]; - public marketData: AdminMarketDataItem[] = []; + public filters$ = new Subject(); + public isLoading = false; + public placeholder = ''; public user: User; private unsubscribeSubject = new Subject(); @@ -98,7 +119,29 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; - this.fetchAdminMarketData(); + this.filters$ + .pipe( + distinctUntilChanged(), + switchMap((filters) => { + this.isLoading = true; + this.activeFilters = filters; + this.placeholder = + this.activeFilters.length <= 0 ? $localize`Filter by...` : ''; + + return this.dataService.fetchAdminMarketData({ + filters: this.activeFilters + }); + }), + takeUntil(this.unsubscribeSubject) + ) + .subscribe(({ marketData }) => { + this.dataSource = new MatTableDataSource(marketData); + this.dataSource.sort = this.sort; + + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + }); } public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { @@ -142,19 +185,6 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } - private fetchAdminMarketData() { - this.dataService - .fetchAdminMarketData() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ marketData }) => { - this.dataSource = new MatTableDataSource(marketData); - - this.dataSource.sort = this.sort; - - this.changeDetectorRef.markForCheck(); - }); - } - private openAssetProfileDialog({ dataSource, dateOfFirstActivity, diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html index d116a0e23..5ae602b3c 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.html +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -1,4 +1,14 @@
+
+
+ +
+
(); public constructor( @@ -133,7 +132,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { this.isLoading = true; this.activeFilters = filters; this.placeholder = - this.activeFilters.length <= 0 ? this.SEARCH_PLACEHOLDER : ''; + this.activeFilters.length <= 0 + ? $localize`Filter by account or tag...` + : ''; return this.dataService.fetchPortfolioDetails({ filters: this.activeFilters diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 1aeb52298..15e3ff115 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -133,8 +133,32 @@ export class DataService { return this.http.get('/api/v1/admin'); } - public fetchAdminMarketData() { - return this.http.get('/api/v1/admin/market-data'); + public fetchAdminMarketData({ filters }: { filters?: Filter[] }) { + let params = new HttpParams(); + + if (filters?.length > 0) { + const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy( + filters, + (filter) => { + return filter.type; + } + ); + + if (filtersByAssetSubClass) { + params = params.append( + 'assetSubClasses', + filtersByAssetSubClass + .map(({ id }) => { + return id; + }) + .join(',') + ); + } + } + + return this.http.get('/api/v1/admin/market-data', { + params + }); } public deleteAccess(aId: string) { diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index aae2cc05f..17c3f6efd 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -186,7 +186,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 122 + 132 apps/client/src/app/components/admin-users/admin-users.html @@ -222,7 +222,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 14 + 24 apps/client/src/app/components/positions-table/positions-table.component.html @@ -242,7 +242,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 23 + 33 apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html @@ -406,7 +406,7 @@ Erste Aktivität apps/client/src/app/components/admin-market-data/admin-market-data.html - 50 + 60 @@ -414,7 +414,7 @@ Anzahl Aktivitäten apps/client/src/app/components/admin-market-data/admin-market-data.html - 59 + 69 apps/client/src/app/components/admin-overview/admin-overview.html @@ -426,7 +426,7 @@ Historische Daten apps/client/src/app/components/admin-market-data/admin-market-data.html - 68 + 78 @@ -434,7 +434,7 @@ Daten einholen apps/client/src/app/components/admin-market-data/admin-market-data.html - 109 + 119 @@ -514,7 +514,7 @@ Profildaten einholen apps/client/src/app/components/admin-market-data/admin-market-data.html - 115 + 125 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1990,7 +1990,7 @@ Anlageklasse apps/client/src/app/components/admin-market-data/admin-market-data.html - 32 + 42 apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html @@ -2386,7 +2386,7 @@ Anlageunterklasse apps/client/src/app/components/admin-market-data/admin-market-data.html - 41 + 51 apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html @@ -2574,7 +2574,7 @@ Anzahl Länder apps/client/src/app/components/admin-market-data/admin-market-data.html - 77 + 87 @@ -2582,7 +2582,7 @@ Anzahl Sektoren apps/client/src/app/components/admin-market-data/admin-market-data.html - 86 + 96 @@ -2601,6 +2601,22 @@ 25 + + Filter by... + Filtern nach... + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 129 + + + + Filter by account or tag... + Filtern nach Konto oder Tag... + + apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts + 136 + + diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 5a3f7440a..dd6ee9dbc 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -174,7 +174,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 122 + 132 apps/client/src/app/components/admin-users/admin-users.html @@ -207,7 +207,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 14 + 24 apps/client/src/app/components/positions-table/positions-table.component.html @@ -226,7 +226,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 23 + 33 apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html @@ -375,14 +375,14 @@ First Activity apps/client/src/app/components/admin-market-data/admin-market-data.html - 50 + 60 Activity Count apps/client/src/app/components/admin-market-data/admin-market-data.html - 59 + 69 apps/client/src/app/components/admin-overview/admin-overview.html @@ -393,14 +393,14 @@ Historical Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 68 + 78 Gather Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 109 + 119 @@ -470,7 +470,7 @@ Gather Profile Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 115 + 125 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1783,7 +1783,7 @@ Asset Class apps/client/src/app/components/admin-market-data/admin-market-data.html - 32 + 42 apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html @@ -2114,7 +2114,7 @@ Asset Sub Class apps/client/src/app/components/admin-market-data/admin-market-data.html - 41 + 51 apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html @@ -2299,14 +2299,14 @@ Sectors Count apps/client/src/app/components/admin-market-data/admin-market-data.html - 86 + 96 Countries Count apps/client/src/app/components/admin-market-data/admin-market-data.html - 77 + 87 @@ -2323,6 +2323,20 @@ 25 + + Filter by account or tag... + + apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts + 136 + + + + Filter by... + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 129 + + \ No newline at end of file diff --git a/libs/common/src/lib/interfaces/filter.interface.ts b/libs/common/src/lib/interfaces/filter.interface.ts index a3e5163e7..6695de883 100644 --- a/libs/common/src/lib/interfaces/filter.interface.ts +++ b/libs/common/src/lib/interfaces/filter.interface.ts @@ -1,5 +1,5 @@ export interface Filter { id: string; label?: string; - type: 'ACCOUNT' | 'ASSET_CLASS' | 'SYMBOL' | 'TAG'; + type: 'ACCOUNT' | 'ASSET_CLASS' | 'ASSET_SUB_CLASS' | 'SYMBOL' | 'TAG'; }