Feature/add queries to market data table in admin control (#2153)
* Add queries * ETF_WITHOUT_COUNTRIES * ETF_WITHOUT_SECTORS * Update changelog
This commit is contained in:
parent
81ef95e13e
commit
68a9a7f6f9
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added hints to the activity types in the create or edit activity dialog
|
- 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
|
### Changed
|
||||||
|
|
||||||
|
@ -15,7 +15,10 @@ import {
|
|||||||
Filter
|
Filter
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
import type {
|
||||||
|
MarketDataQuery,
|
||||||
|
RequestWithUser
|
||||||
|
} from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
@ -249,6 +252,7 @@ export class AdminController {
|
|||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getMarketData(
|
public async getMarketData(
|
||||||
@Query('assetSubClasses') filterByAssetSubClasses?: string,
|
@Query('assetSubClasses') filterByAssetSubClasses?: string,
|
||||||
|
@Query('queryId') queryId?: MarketDataQuery,
|
||||||
@Query('skip') skip?: number,
|
@Query('skip') skip?: number,
|
||||||
@Query('sortColumn') sortColumn?: string,
|
@Query('sortColumn') sortColumn?: string,
|
||||||
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
|
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
|
||||||
@ -279,6 +283,7 @@ export class AdminController {
|
|||||||
|
|
||||||
return this.adminService.getMarketData({
|
return this.adminService.getMarketData({
|
||||||
filters,
|
filters,
|
||||||
|
queryId,
|
||||||
sortColumn,
|
sortColumn,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
skip: isNaN(skip) ? undefined : skip,
|
skip: isNaN(skip) ? undefined : skip,
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
UniqueAsset
|
UniqueAsset
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
import { MarketDataQuery } from '@ghostfolio/common/types';
|
||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import { AssetSubClass, Prisma, Property, SymbolProfile } from '@prisma/client';
|
import { AssetSubClass, Prisma, Property, SymbolProfile } from '@prisma/client';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
@ -103,12 +104,14 @@ export class AdminService {
|
|||||||
|
|
||||||
public async getMarketData({
|
public async getMarketData({
|
||||||
filters,
|
filters,
|
||||||
|
queryId,
|
||||||
sortColumn,
|
sortColumn,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
skip,
|
skip,
|
||||||
take = DEFAULT_PAGE_SIZE
|
take = Number.MAX_SAFE_INTEGER
|
||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
|
queryId?: MarketDataQuery;
|
||||||
skip?: number;
|
skip?: number;
|
||||||
sortColumn?: string;
|
sortColumn?: string;
|
||||||
sortDirection?: Prisma.SortOrder;
|
sortDirection?: Prisma.SortOrder;
|
||||||
@ -118,6 +121,13 @@ export class AdminService {
|
|||||||
[{ symbol: 'asc' }];
|
[{ symbol: 'asc' }];
|
||||||
const where: Prisma.SymbolProfileWhereInput = {};
|
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(
|
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
|
||||||
filters,
|
filters,
|
||||||
(filter) => {
|
(filter) => {
|
||||||
@ -146,7 +156,7 @@ export class AdminService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [assetProfiles, count] = await Promise.all([
|
let [assetProfiles, count] = await Promise.all([
|
||||||
this.prismaService.symbolProfile.findMany({
|
this.prismaService.symbolProfile.findMany({
|
||||||
orderBy,
|
orderBy,
|
||||||
skip,
|
skip,
|
||||||
@ -174,44 +184,60 @@ export class AdminService {
|
|||||||
this.prismaService.symbolProfile.count({ where })
|
this.prismaService.symbolProfile.count({ where })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
let marketData = assetProfiles.map(
|
||||||
count,
|
({
|
||||||
marketData: assetProfiles.map(
|
_count,
|
||||||
({
|
assetClass,
|
||||||
_count,
|
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,
|
assetClass,
|
||||||
assetSubClass,
|
assetSubClass,
|
||||||
comment,
|
comment,
|
||||||
countries,
|
countriesCount,
|
||||||
dataSource,
|
dataSource,
|
||||||
Order,
|
symbol,
|
||||||
sectors,
|
marketDataItemCount,
|
||||||
symbol
|
sectorsCount,
|
||||||
}) => {
|
activitiesCount: _count.Order,
|
||||||
const countriesCount = countries ? Object.keys(countries).length : 0;
|
date: Order?.[0]?.date
|
||||||
const marketDataItemCount =
|
};
|
||||||
marketDataItems.find((marketDataItem) => {
|
}
|
||||||
return (
|
);
|
||||||
marketDataItem.dataSource === dataSource &&
|
|
||||||
marketDataItem.symbol === symbol
|
|
||||||
);
|
|
||||||
})?._count ?? 0;
|
|
||||||
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
|
|
||||||
|
|
||||||
return {
|
if (queryId) {
|
||||||
assetClass,
|
if (queryId === 'ETF_WITHOUT_COUNTRIES') {
|
||||||
assetSubClass,
|
marketData = marketData.filter(({ countriesCount }) => {
|
||||||
comment,
|
return countriesCount === 0;
|
||||||
countriesCount,
|
});
|
||||||
dataSource,
|
} else if (queryId === 'ETF_WITHOUT_SECTORS') {
|
||||||
symbol,
|
marketData = marketData.filter(({ sectorsCount }) => {
|
||||||
marketDataItemCount,
|
return sectorsCount === 0;
|
||||||
sectorsCount,
|
});
|
||||||
activitiesCount: _count.Order,
|
}
|
||||||
date: Order?.[0]?.date
|
|
||||||
};
|
count = marketData.length;
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
return {
|
||||||
|
count,
|
||||||
|
marketData
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,13 +51,26 @@ export class AdminMarketDataComponent
|
|||||||
AssetSubClass.PRECIOUS_METAL,
|
AssetSubClass.PRECIOUS_METAL,
|
||||||
AssetSubClass.PRIVATE_EQUITY,
|
AssetSubClass.PRIVATE_EQUITY,
|
||||||
AssetSubClass.STOCK
|
AssetSubClass.STOCK
|
||||||
].map((assetSubClass) => {
|
]
|
||||||
return {
|
.map((assetSubClass) => {
|
||||||
id: assetSubClass,
|
return {
|
||||||
label: translate(assetSubClass),
|
id: assetSubClass.toString(),
|
||||||
type: 'ASSET_SUB_CLASS'
|
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 currentDataSource: DataSource;
|
||||||
public currentSymbol: string;
|
public currentSymbol: string;
|
||||||
public dataSource: MatTableDataSource<AdminMarketDataItem> =
|
public dataSource: MatTableDataSource<AdminMarketDataItem> =
|
||||||
@ -237,6 +250,12 @@ export class AdminMarketDataComponent
|
|||||||
) {
|
) {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.pageSize =
|
||||||
|
this.activeFilters.length === 1 &&
|
||||||
|
this.activeFilters[0].type === 'QUERY_ID'
|
||||||
|
? undefined
|
||||||
|
: DEFAULT_PAGE_SIZE;
|
||||||
|
|
||||||
if (pageIndex === 0 && this.paginator) {
|
if (pageIndex === 0 && this.paginator) {
|
||||||
this.paginator.pageIndex = 0;
|
this.paginator.pageIndex = 0;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,9 @@ export class AdminService {
|
|||||||
params = params.append('sortDirection', sortDirection);
|
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', {
|
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
|
||||||
params
|
params
|
||||||
|
@ -57,6 +57,7 @@ export class DataService {
|
|||||||
ACCOUNT: filtersByAccount,
|
ACCOUNT: filtersByAccount,
|
||||||
ASSET_CLASS: filtersByAssetClass,
|
ASSET_CLASS: filtersByAssetClass,
|
||||||
ASSET_SUB_CLASS: filtersByAssetSubClass,
|
ASSET_SUB_CLASS: filtersByAssetSubClass,
|
||||||
|
QUERY_ID: filtersByQueryId,
|
||||||
TAG: filtersByTag
|
TAG: filtersByTag
|
||||||
} = groupBy(filters, (filter) => {
|
} = groupBy(filters, (filter) => {
|
||||||
return filter.type;
|
return filter.type;
|
||||||
@ -95,6 +96,10 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filtersByQueryId) {
|
||||||
|
params = params.append('queryId', filtersByQueryId[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
if (filtersByTag) {
|
if (filtersByTag) {
|
||||||
params = params.append(
|
params = params.append(
|
||||||
'tags',
|
'tags',
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
export interface Filter {
|
export interface Filter {
|
||||||
id: string;
|
id: string;
|
||||||
label?: 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 { DateRange } from './date-range.type';
|
||||||
import type { Granularity } from './granularity.type';
|
import type { Granularity } from './granularity.type';
|
||||||
import type { GroupBy } from './group-by.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 { MarketState } from './market-state.type';
|
||||||
import type { Market } from './market.type';
|
import type { Market } from './market.type';
|
||||||
import type { OrderWithAccount } from './order-with-account.type';
|
import type { OrderWithAccount } from './order-with-account.type';
|
||||||
@ -23,6 +24,7 @@ export type {
|
|||||||
Granularity,
|
Granularity,
|
||||||
GroupBy,
|
GroupBy,
|
||||||
Market,
|
Market,
|
||||||
|
MarketDataQuery,
|
||||||
MarketState,
|
MarketState,
|
||||||
OrderWithAccount,
|
OrderWithAccount,
|
||||||
RequestWithUser,
|
RequestWithUser,
|
||||||
|
1
libs/common/src/lib/types/market-data-query.type.ts
Normal file
1
libs/common/src/lib/types/market-data-query.type.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type MarketDataQuery = 'ETF_WITHOUT_COUNTRIES' | 'ETF_WITHOUT_SECTORS';
|
@ -16,6 +16,7 @@ const locales = {
|
|||||||
MONTH: $localize`Month`,
|
MONTH: $localize`Month`,
|
||||||
MONTHS: $localize`Months`,
|
MONTHS: $localize`Months`,
|
||||||
OTHER: $localize`Other`,
|
OTHER: $localize`Other`,
|
||||||
|
QUERY_ID: $localize`Query`,
|
||||||
RETIREMENT_PROVISION: $localize`Retirement Provision`,
|
RETIREMENT_PROVISION: $localize`Retirement Provision`,
|
||||||
SATELLITE: $localize`Satellite`,
|
SATELLITE: $localize`Satellite`,
|
||||||
SECURITIES: $localize`Securities`,
|
SECURITIES: $localize`Securities`,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user