Browse Source
Feature/support filtering by asset class on the allocations page (#926)
* Support filtering by asset class
* Update changelog
pull/927/head
Thomas Kaul
3 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with
96 additions and
23 deletions
-
CHANGELOG.md
-
apps/api/src/app/account/account.service.ts
-
apps/api/src/app/order/order.service.ts
-
apps/api/src/app/portfolio/portfolio.controller.ts
-
apps/api/src/app/portfolio/portfolio.service.ts
-
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
-
apps/client/src/app/services/data.service.ts
-
libs/common/src/lib/interfaces/filter.interface.ts
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 |
|
|
|
### Added |
|
|
|
|
|
|
|
- Added groups to the activities filter component |
|
|
|
- Added support for filtering by asset class on the allocations page |
|
|
|
|
|
|
|
## 1.148.0 - 14.05.2022 |
|
|
|
|
|
|
|
|
|
@ -4,6 +4,7 @@ import { Filter } from '@ghostfolio/common/interfaces'; |
|
|
|
import { Injectable } from '@nestjs/common'; |
|
|
|
import { Account, Order, Platform, Prisma } from '@prisma/client'; |
|
|
|
import Big from 'big.js'; |
|
|
|
import { groupBy } from 'lodash'; |
|
|
|
|
|
|
|
import { CashDetails } from './interfaces/cash-details.interface'; |
|
|
|
|
|
|
@ -116,15 +117,19 @@ export class AccountService { |
|
|
|
|
|
|
|
const where: Prisma.AccountWhereInput = { userId }; |
|
|
|
|
|
|
|
if (filters?.length > 0) { |
|
|
|
const { |
|
|
|
ACCOUNT: filtersByAccount, |
|
|
|
ASSET_CLASS: filtersByAssetClass, |
|
|
|
TAG: filtersByTag |
|
|
|
} = groupBy(filters, (filter) => { |
|
|
|
return filter.type; |
|
|
|
}); |
|
|
|
|
|
|
|
if (filtersByAccount?.length > 0) { |
|
|
|
where.id = { |
|
|
|
in: filters |
|
|
|
.filter(({ type }) => { |
|
|
|
return type === 'ACCOUNT'; |
|
|
|
}) |
|
|
|
.map(({ id }) => { |
|
|
|
return id; |
|
|
|
}) |
|
|
|
in: filtersByAccount.map(({ id }) => { |
|
|
|
return id; |
|
|
|
}) |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -188,12 +188,13 @@ export class OrderService { |
|
|
|
}): Promise<Activity[]> { |
|
|
|
const where: Prisma.OrderWhereInput = { userId }; |
|
|
|
|
|
|
|
const { ACCOUNT: filtersByAccount, TAG: filtersByTag } = groupBy( |
|
|
|
filters, |
|
|
|
(filter) => { |
|
|
|
return filter.type; |
|
|
|
} |
|
|
|
); |
|
|
|
const { |
|
|
|
ACCOUNT: filtersByAccount, |
|
|
|
ASSET_CLASS: filtersByAssetClass, |
|
|
|
TAG: filtersByTag |
|
|
|
} = groupBy(filters, (filter) => { |
|
|
|
return filter.type; |
|
|
|
}); |
|
|
|
|
|
|
|
if (filtersByAccount?.length > 0) { |
|
|
|
where.accountId = { |
|
|
@ -207,6 +208,34 @@ export class OrderService { |
|
|
|
where.isDraft = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (filtersByAssetClass?.length > 0) { |
|
|
|
where.SymbolProfile = { |
|
|
|
OR: [ |
|
|
|
{ |
|
|
|
AND: [ |
|
|
|
{ |
|
|
|
OR: filtersByAssetClass.map(({ id }) => { |
|
|
|
return { assetClass: AssetClass[id] }; |
|
|
|
}) |
|
|
|
}, |
|
|
|
{ |
|
|
|
SymbolProfileOverrides: { |
|
|
|
is: null |
|
|
|
} |
|
|
|
} |
|
|
|
] |
|
|
|
}, |
|
|
|
{ |
|
|
|
SymbolProfileOverrides: { |
|
|
|
OR: filtersByAssetClass.map(({ id }) => { |
|
|
|
return { assetClass: AssetClass[id] }; |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
] |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
if (filtersByTag?.length > 0) { |
|
|
|
where.tags = { |
|
|
|
some: { |
|
|
|
|
|
@ -107,12 +107,14 @@ export class PortfolioController { |
|
|
|
public async getDetails( |
|
|
|
@Headers('impersonation-id') impersonationId: string, |
|
|
|
@Query('accounts') filterByAccounts?: string, |
|
|
|
@Query('assetClasses') filterByAssetClasses?: string, |
|
|
|
@Query('range') range?: DateRange, |
|
|
|
@Query('tags') filterByTags?: string |
|
|
|
): Promise<PortfolioDetails & { hasError: boolean }> { |
|
|
|
let hasError = false; |
|
|
|
|
|
|
|
const accountIds = filterByAccounts?.split(',') ?? []; |
|
|
|
const assetClasses = filterByAssetClasses?.split(',') ?? []; |
|
|
|
const tagIds = filterByTags?.split(',') ?? []; |
|
|
|
|
|
|
|
const filters: Filter[] = [ |
|
|
@ -122,6 +124,12 @@ export class PortfolioController { |
|
|
|
type: 'ACCOUNT' |
|
|
|
}; |
|
|
|
}), |
|
|
|
...assetClasses.map((assetClass) => { |
|
|
|
return <Filter>{ |
|
|
|
id: assetClass, |
|
|
|
type: 'ASSET_CLASS' |
|
|
|
}; |
|
|
|
}), |
|
|
|
...tagIds.map((tagId) => { |
|
|
|
return <Filter>{ |
|
|
|
id: tagId, |
|
|
|
|
|
@ -441,7 +441,12 @@ export class PortfolioService { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
if (aFilters?.length === 0) { |
|
|
|
if ( |
|
|
|
aFilters?.length === 0 || |
|
|
|
(aFilters?.length === 1 && |
|
|
|
aFilters[0].type === 'ASSET_CLASS' && |
|
|
|
aFilters[0].id === 'CASH') |
|
|
|
) { |
|
|
|
const cashPositions = await this.getCashPositions({ |
|
|
|
cashDetails, |
|
|
|
emergencyFund, |
|
|
|
|
|
@ -172,6 +172,15 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
const assetClassFilters: Filter[] = []; |
|
|
|
for (const assetClass of Object.keys(AssetClass)) { |
|
|
|
assetClassFilters.push({ |
|
|
|
id: assetClass, |
|
|
|
label: assetClass, |
|
|
|
type: 'ASSET_CLASS' |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => { |
|
|
|
return { |
|
|
|
id, |
|
|
@ -180,7 +189,11 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
this.allFilters = [...accountFilters, ...tagFilters]; |
|
|
|
this.allFilters = [ |
|
|
|
...accountFilters, |
|
|
|
...assetClassFilters, |
|
|
|
...tagFilters |
|
|
|
]; |
|
|
|
|
|
|
|
this.changeDetectorRef.markForCheck(); |
|
|
|
} |
|
|
|
|
|
@ -187,12 +187,13 @@ export class DataService { |
|
|
|
let params = new HttpParams(); |
|
|
|
|
|
|
|
if (filters?.length > 0) { |
|
|
|
const { ACCOUNT: filtersByAccount, TAG: filtersByTag } = groupBy( |
|
|
|
filters, |
|
|
|
(filter) => { |
|
|
|
return filter.type; |
|
|
|
} |
|
|
|
); |
|
|
|
const { |
|
|
|
ACCOUNT: filtersByAccount, |
|
|
|
ASSET_CLASS: filtersByAssetClass, |
|
|
|
TAG: filtersByTag |
|
|
|
} = groupBy(filters, (filter) => { |
|
|
|
return filter.type; |
|
|
|
}); |
|
|
|
|
|
|
|
if (filtersByAccount) { |
|
|
|
params = params.append( |
|
|
@ -205,6 +206,17 @@ export class DataService { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (filtersByAssetClass) { |
|
|
|
params = params.append( |
|
|
|
'assetClasses', |
|
|
|
filtersByAssetClass |
|
|
|
.map(({ id }) => { |
|
|
|
return id; |
|
|
|
}) |
|
|
|
.join(',') |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (filtersByTag) { |
|
|
|
params = params.append( |
|
|
|
'tags', |
|
|
|
|
|
@ -1,5 +1,5 @@ |
|
|
|
export interface Filter { |
|
|
|
id: string; |
|
|
|
label?: string; |
|
|
|
type: 'ACCOUNT' | 'SYMBOL' | 'TAG'; |
|
|
|
type: 'ACCOUNT' | 'ASSET_CLASS' | 'SYMBOL' | 'TAG'; |
|
|
|
} |
|
|
|