Feature/add queries to market data table in admin control (#2153)

* Add queries

* ETF_WITHOUT_COUNTRIES
* ETF_WITHOUT_SECTORS

* Update changelog
pull/2154/head
Thomas Kaul 11 months ago committed by GitHub
parent 81ef95e13e
commit 68a9a7f6f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added hints to the activity types in the create or edit activity dialog
- Added queries to the historical market data table of the admin control panel
### Changed

@ -15,7 +15,10 @@ import {
Filter
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import type {
MarketDataQuery,
RequestWithUser
} from '@ghostfolio/common/types';
import {
Body,
Controller,
@ -249,6 +252,7 @@ export class AdminController {
@UseGuards(AuthGuard('jwt'))
public async getMarketData(
@Query('assetSubClasses') filterByAssetSubClasses?: string,
@Query('queryId') queryId?: MarketDataQuery,
@Query('skip') skip?: number,
@Query('sortColumn') sortColumn?: string,
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
@ -279,6 +283,7 @@ export class AdminController {
return this.adminService.getMarketData({
filters,
queryId,
sortColumn,
sortDirection,
skip: isNaN(skip) ? undefined : skip,

@ -17,6 +17,7 @@ import {
Filter,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { MarketDataQuery } from '@ghostfolio/common/types';
import { BadRequestException, Injectable } from '@nestjs/common';
import { AssetSubClass, Prisma, Property, SymbolProfile } from '@prisma/client';
import { differenceInDays } from 'date-fns';
@ -103,12 +104,14 @@ export class AdminService {
public async getMarketData({
filters,
queryId,
sortColumn,
sortDirection,
skip,
take = DEFAULT_PAGE_SIZE
take = Number.MAX_SAFE_INTEGER
}: {
filters?: Filter[];
queryId?: MarketDataQuery;
skip?: number;
sortColumn?: string;
sortDirection?: Prisma.SortOrder;
@ -118,6 +121,13 @@ export class AdminService {
[{ symbol: 'asc' }];
const where: Prisma.SymbolProfileWhereInput = {};
if (
queryId === 'ETF_WITHOUT_COUNTRIES' ||
queryId === 'ETF_WITHOUT_SECTORS'
) {
filters = [{ id: 'ETF', type: 'ASSET_SUB_CLASS' }];
}
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
filters,
(filter) => {
@ -146,7 +156,7 @@ export class AdminService {
}
}
const [assetProfiles, count] = await Promise.all([
let [assetProfiles, count] = await Promise.all([
this.prismaService.symbolProfile.findMany({
orderBy,
skip,
@ -174,44 +184,60 @@ export class AdminService {
this.prismaService.symbolProfile.count({ where })
]);
return {
count,
marketData: assetProfiles.map(
({
_count,
let marketData = assetProfiles.map(
({
_count,
assetClass,
assetSubClass,
comment,
countries,
dataSource,
Order,
sectors,
symbol
}) => {
const countriesCount = countries ? Object.keys(countries).length : 0;
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
);
})?._count ?? 0;
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
return {
assetClass,
assetSubClass,
comment,
countries,
countriesCount,
dataSource,
Order,
sectors,
symbol
}) => {
const countriesCount = countries ? Object.keys(countries).length : 0;
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
);
})?._count ?? 0;
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
symbol,
marketDataItemCount,
sectorsCount,
activitiesCount: _count.Order,
date: Order?.[0]?.date
};
}
);
return {
assetClass,
assetSubClass,
comment,
countriesCount,
dataSource,
symbol,
marketDataItemCount,
sectorsCount,
activitiesCount: _count.Order,
date: Order?.[0]?.date
};
}
)
if (queryId) {
if (queryId === 'ETF_WITHOUT_COUNTRIES') {
marketData = marketData.filter(({ countriesCount }) => {
return countriesCount === 0;
});
} else if (queryId === 'ETF_WITHOUT_SECTORS') {
marketData = marketData.filter(({ sectorsCount }) => {
return sectorsCount === 0;
});
}
count = marketData.length;
}
return {
count,
marketData
};
}

@ -51,13 +51,26 @@ export class AdminMarketDataComponent
AssetSubClass.PRECIOUS_METAL,
AssetSubClass.PRIVATE_EQUITY,
AssetSubClass.STOCK
].map((assetSubClass) => {
return {
id: assetSubClass,
label: translate(assetSubClass),
type: 'ASSET_SUB_CLASS'
};
});
]
.map((assetSubClass) => {
return {
id: assetSubClass.toString(),
label: translate(assetSubClass),
type: <Filter['type']>'ASSET_SUB_CLASS'
};
})
.concat([
{
id: 'ETF_WITHOUT_COUNTRIES',
label: $localize`ETFs without Countries`,
type: <Filter['type']>'QUERY_ID'
},
{
id: 'ETF_WITHOUT_SECTORS',
label: $localize`ETFs without Sectors`,
type: <Filter['type']>'QUERY_ID'
}
]);
public currentDataSource: DataSource;
public currentSymbol: string;
public dataSource: MatTableDataSource<AdminMarketDataItem> =
@ -237,6 +250,12 @@ export class AdminMarketDataComponent
) {
this.isLoading = true;
this.pageSize =
this.activeFilters.length === 1 &&
this.activeFilters[0].type === 'QUERY_ID'
? undefined
: DEFAULT_PAGE_SIZE;
if (pageIndex === 0 && this.paginator) {
this.paginator.pageIndex = 0;
}

@ -95,7 +95,9 @@ export class AdminService {
params = params.append('sortDirection', sortDirection);
}
params = params.append('take', take);
if (take) {
params = params.append('take', take);
}
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
params

@ -57,6 +57,7 @@ export class DataService {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
ASSET_SUB_CLASS: filtersByAssetSubClass,
QUERY_ID: filtersByQueryId,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
@ -95,6 +96,10 @@ export class DataService {
);
}
if (filtersByQueryId) {
params = params.append('queryId', filtersByQueryId[0].id);
}
if (filtersByTag) {
params = params.append(
'tags',

@ -1,5 +1,11 @@
export interface Filter {
id: string;
label?: string;
type: 'ACCOUNT' | 'ASSET_CLASS' | 'ASSET_SUB_CLASS' | 'SYMBOL' | 'TAG';
type:
| 'ACCOUNT'
| 'ASSET_CLASS'
| 'ASSET_SUB_CLASS'
| 'QUERY_ID'
| 'SYMBOL'
| 'TAG';
}

@ -5,6 +5,7 @@ import type { ColorScheme } from './color-scheme.type';
import type { DateRange } from './date-range.type';
import type { Granularity } from './granularity.type';
import type { GroupBy } from './group-by.type';
import type { MarketDataQuery } from './market-data-query.type';
import type { MarketState } from './market-state.type';
import type { Market } from './market.type';
import type { OrderWithAccount } from './order-with-account.type';
@ -23,6 +24,7 @@ export type {
Granularity,
GroupBy,
Market,
MarketDataQuery,
MarketState,
OrderWithAccount,
RequestWithUser,

@ -0,0 +1 @@
export type MarketDataQuery = 'ETF_WITHOUT_COUNTRIES' | 'ETF_WITHOUT_SECTORS';

@ -16,6 +16,7 @@ const locales = {
MONTH: $localize`Month`,
MONTHS: $localize`Months`,
OTHER: $localize`Other`,
QUERY_ID: $localize`Query`,
RETIREMENT_PROVISION: $localize`Retirement Provision`,
SATELLITE: $localize`Satellite`,
SECURITIES: $localize`Securities`,

Loading…
Cancel
Save