Browse Source

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 1 year ago
committed by GitHub
parent
commit
68a9a7f6f9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 7
      apps/api/src/app/admin/admin.controller.ts
  3. 96
      apps/api/src/app/admin/admin.service.ts
  4. 33
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  5. 4
      apps/client/src/app/services/admin.service.ts
  6. 5
      apps/client/src/app/services/data.service.ts
  7. 8
      libs/common/src/lib/interfaces/filter.interface.ts
  8. 2
      libs/common/src/lib/types/index.ts
  9. 1
      libs/common/src/lib/types/market-data-query.type.ts
  10. 1
      libs/ui/src/lib/i18n.ts

1
CHANGELOG.md

@ -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

7
apps/api/src/app/admin/admin.controller.ts

@ -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,

96
apps/api/src/app/admin/admin.service.ts

@ -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
}; };
} }

33
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -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;
} }

4
apps/client/src/app/services/admin.service.ts

@ -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

5
apps/client/src/app/services/data.service.ts

@ -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',

8
libs/common/src/lib/interfaces/filter.interface.ts

@ -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';
} }

2
libs/common/src/lib/types/index.ts

@ -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

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

1
libs/ui/src/lib/i18n.ts

@ -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…
Cancel
Save