Feature/introduce filters in account endpoint (#3764)

* Introduce filters in acount endpoint

* Integrate endpoint in holding detail dialog

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/3765/head
Shaunak Das 3 months ago committed by GitHub
parent 8735fc3fad
commit 323cfbfcaa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Introduced filters (`dataSource` and `symbol`) in the accounts endpoint
### Changed
- Switched to the accounts endpoint in the holding detail dialog
## 2.107.1 - 2024-09-12 ## 2.107.1 - 2024-09-12
### Fixed ### Fixed

@ -3,6 +3,8 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.servic
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { import {
@ -26,6 +28,7 @@ import {
Param, Param,
Post, Post,
Put, Put,
Query,
UseGuards, UseGuards,
UseInterceptors UseInterceptors
} from '@nestjs/common'; } from '@nestjs/common';
@ -44,6 +47,7 @@ export class AccountController {
public constructor( public constructor(
private readonly accountBalanceService: AccountBalanceService, private readonly accountBalanceService: AccountBalanceService,
private readonly accountService: AccountService, private readonly accountService: AccountService,
private readonly apiService: ApiService,
private readonly impersonationService: ImpersonationService, private readonly impersonationService: ImpersonationService,
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser @Inject(REQUEST) private readonly request: RequestWithUser
@ -84,13 +88,22 @@ export class AccountController {
@Get() @Get()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(RedactValuesInResponseInterceptor)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getAllAccounts( public async getAllAccounts(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string
): Promise<Accounts> { ): Promise<Accounts> {
const impersonationUserId = const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId); await this.impersonationService.validateImpersonationId(impersonationId);
const filters = this.apiService.buildFiltersFromQueryParams({
filterByDataSource,
filterBySymbol
});
return this.portfolioService.getAccountsWithAggregations({ return this.portfolioService.getAccountsWithAggregations({
filters,
userId: impersonationUserId || this.request.user.id, userId: impersonationUserId || this.request.user.id,
withExcludedAccounts: true withExcludedAccounts: true
}); });

@ -1,6 +1,7 @@
import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module'; import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module';
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
@ -16,6 +17,7 @@ import { AccountService } from './account.service';
exports: [AccountService], exports: [AccountService],
imports: [ imports: [
AccountBalanceModule, AccountBalanceModule,
ApiModule,
ConfigurationModule, ConfigurationModule,
ExchangeRateDataModule, ExchangeRateDataModule,
ImpersonationModule, ImpersonationModule,

@ -115,12 +115,33 @@ export class PortfolioService {
}): Promise<AccountWithValue[]> { }): Promise<AccountWithValue[]> {
const where: Prisma.AccountWhereInput = { userId }; const where: Prisma.AccountWhereInput = { userId };
const accountFilter = filters?.find(({ type }) => { const filterByAccount = filters?.find(({ type }) => {
return type === 'ACCOUNT'; return type === 'ACCOUNT';
}); })?.id;
const filterByDataSource = filters?.find(({ type }) => {
return type === 'DATA_SOURCE';
})?.id;
const filterBySymbol = filters?.find(({ type }) => {
return type === 'SYMBOL';
})?.id;
if (accountFilter) { if (filterByAccount) {
where.id = accountFilter.id; where.id = filterByAccount;
}
if (filterByDataSource && filterBySymbol) {
where.Order = {
some: {
SymbolProfile: {
AND: [
{ dataSource: <DataSource>filterByDataSource },
{ symbol: filterBySymbol }
]
}
}
};
} }
const [accounts, details] = await Promise.all([ const [accounts, details] = await Promise.all([

@ -9,6 +9,7 @@ import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import { import {
DataProviderInfo, DataProviderInfo,
EnhancedSymbolProfile, EnhancedSymbolProfile,
Filter,
LineChartItem, LineChartItem,
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
@ -152,6 +153,11 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
tags: <string[]>[] tags: <string[]>[]
}); });
const filters: Filter[] = [
{ id: this.data.dataSource, type: 'DATA_SOURCE' },
{ id: this.data.symbol, type: 'SYMBOL' }
];
this.tagsAvailable = tags.map(({ id, name }) => { this.tagsAvailable = tags.map(({ id, name }) => {
return { return {
id, id,
@ -173,12 +179,20 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
.subscribe(); .subscribe();
}); });
this.dataService
.fetchAccounts({
filters
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ accounts }) => {
this.accounts = accounts;
this.changeDetectorRef.markForCheck();
});
this.dataService this.dataService
.fetchActivities({ .fetchActivities({
filters: [ filters,
{ id: this.data.dataSource, type: 'DATA_SOURCE' },
{ id: this.data.symbol, type: 'SYMBOL' }
],
sortColumn: this.sortColumn, sortColumn: this.sortColumn,
sortDirection: this.sortDirection sortDirection: this.sortDirection
}) })
@ -197,7 +211,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe( .subscribe(
({ ({
accounts,
averagePrice, averagePrice,
dataProviderInfo, dataProviderInfo,
dividendInBaseCurrency, dividendInBaseCurrency,
@ -219,7 +232,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
transactionCount, transactionCount,
value value
}) => { }) => {
this.accounts = accounts;
this.averagePrice = averagePrice; this.averagePrice = averagePrice;
this.benchmarkDataItems = []; this.benchmarkDataItems = [];
this.countries = {}; this.countries = {};

@ -173,8 +173,10 @@ export class DataService {
); );
} }
public fetchAccounts() { public fetchAccounts({ filters }: { filters?: Filter[] } = {}) {
return this.http.get<Accounts>('/api/v1/account'); const params = this.buildFiltersAsQueryParams({ filters });
return this.http.get<Accounts>('/api/v1/account', { params });
} }
public fetchActivities({ public fetchActivities({

Loading…
Cancel
Save