Browse Source

Add pagination

pull/2108/head
Thomas 2 years ago
parent
commit
b1a2aba750
  1. 11
      apps/api/src/app/admin/admin.controller.ts
  2. 101
      apps/api/src/app/admin/admin.service.ts
  3. 71
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  4. 38
      apps/client/src/app/components/admin-market-data/admin-market-data.html
  5. 4
      apps/client/src/app/components/admin-market-data/admin-market-data.module.ts
  6. 4
      apps/client/src/app/components/admin-overview/admin-overview.component.ts
  7. 4
      apps/client/src/app/components/admin-users/admin-users.component.ts
  8. 35
      apps/client/src/app/services/admin.service.ts
  9. 134
      apps/client/src/app/services/data.service.ts
  10. 1
      libs/common/src/lib/interfaces/admin-market-data.interface.ts
  11. 2
      libs/ui/src/lib/activities-table/activities-table.component.html

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

@ -3,6 +3,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/da
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PropertyDto } from '@ghostfolio/api/services/property/property.dto'; import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import { import {
DEFAULT_PAGE_SIZE,
GATHER_ASSET_PROFILE_PROCESS, GATHER_ASSET_PROFILE_PROCESS,
GATHER_ASSET_PROFILE_PROCESS_OPTIONS GATHER_ASSET_PROFILE_PROCESS_OPTIONS
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
@ -247,7 +248,9 @@ export class AdminController {
@Get('market-data') @Get('market-data')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async getMarketData( public async getMarketData(
@Query('assetSubClasses') filterByAssetSubClasses?: string @Query('assetSubClasses') filterByAssetSubClasses?: string,
@Query('skip') skip?: number,
@Query('take') take?: number
): Promise<AdminMarketData> { ): Promise<AdminMarketData> {
if ( if (
!hasPermission( !hasPermission(
@ -272,7 +275,11 @@ export class AdminController {
}) })
]; ];
return this.adminService.getMarketData(filters); return this.adminService.getMarketData({
filters,
skip: isNaN(skip) ? undefined : skip,
take: isNaN(take) ? undefined : take
});
} }
@Get('market-data/:dataSource/:symbol') @Get('market-data/:dataSource/:symbol')

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

@ -6,12 +6,14 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data/market-d
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; import {
DEFAULT_PAGE_SIZE,
PROPERTY_CURRENCIES
} from '@ghostfolio/common/config';
import { import {
AdminData, AdminData,
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem,
Filter, Filter,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
@ -99,7 +101,15 @@ export class AdminService {
}; };
} }
public async getMarketData(filters?: Filter[]): Promise<AdminMarketData> { public async getMarketData({
filters,
skip,
take = DEFAULT_PAGE_SIZE
}: {
filters?: Filter[];
skip?: number;
take?: number;
}): Promise<AdminMarketData> {
const where: Prisma.SymbolProfileWhereInput = {}; const where: Prisma.SymbolProfileWhereInput = {};
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy( const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
@ -109,40 +119,19 @@ export class AdminService {
} }
); );
const marketData = await this.prismaService.marketData.groupBy({ const marketDataItems = await this.prismaService.marketData.groupBy({
_count: true, _count: true,
by: ['dataSource', 'symbol'] by: ['dataSource', 'symbol']
}); });
let currencyPairsToGather: AdminMarketDataItem[] = [];
if (filtersByAssetSubClass) { if (filtersByAssetSubClass) {
where.assetSubClass = AssetSubClass[filtersByAssetSubClass[0].id]; where.assetSubClass = AssetSubClass[filtersByAssetSubClass[0].id];
} else {
currencyPairsToGather = this.exchangeRateDataService
.getCurrencyPairs()
.map(({ dataSource, symbol }) => {
const marketDataItemCount =
marketData.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
);
})?._count ?? 0;
return {
dataSource,
marketDataItemCount,
symbol,
assetClass: 'CASH',
countriesCount: 0,
sectorsCount: 0
};
});
} }
const symbolProfilesToGather: AdminMarketDataItem[] = ( const [assetProfiles, count] = await Promise.all([
await this.prismaService.symbolProfile.findMany({ this.prismaService.symbolProfile.findMany({
skip,
take,
where, where,
orderBy: [{ symbol: 'asc' }], orderBy: [{ symbol: 'asc' }],
select: { select: {
@ -163,38 +152,48 @@ export class AdminService {
sectors: true, sectors: true,
symbol: true symbol: true
} }
}) }),
).map((symbolProfile) => { this.prismaService.symbolProfile.count({ where })
const countriesCount = symbolProfile.countries ]);
? Object.keys(symbolProfile.countries).length
: 0; return {
count,
marketData: assetProfiles.map(
({
_count,
assetClass,
assetSubClass,
comment,
countries,
dataSource,
Order,
sectors,
symbol
}) => {
const countriesCount = countries ? Object.keys(countries).length : 0;
const marketDataItemCount = const marketDataItemCount =
marketData.find((marketDataItem) => { marketDataItems.find((marketDataItem) => {
return ( return (
marketDataItem.dataSource === symbolProfile.dataSource && marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbolProfile.symbol marketDataItem.symbol === symbol
); );
})?._count ?? 0; })?._count ?? 0;
const sectorsCount = symbolProfile.sectors const sectorsCount = sectors ? Object.keys(sectors).length : 0;
? Object.keys(symbolProfile.sectors).length
: 0;
return { return {
assetClass,
assetSubClass,
comment,
countriesCount, countriesCount,
dataSource,
symbol,
marketDataItemCount, marketDataItemCount,
sectorsCount, sectorsCount,
activitiesCount: symbolProfile._count.Order, activitiesCount: _count.Order,
assetClass: symbolProfile.assetClass, date: Order?.[0]?.date
assetSubClass: symbolProfile.assetSubClass,
comment: symbolProfile.comment,
dataSource: symbolProfile.dataSource,
date: symbolProfile.Order?.[0]?.date,
symbol: symbolProfile.symbol
}; };
}); }
)
return {
marketData: [...currencyPairsToGather, ...symbolProfilesToGather]
}; };
} }

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

@ -11,7 +11,6 @@ import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
@ -26,6 +25,8 @@ import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.
import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces'; import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces';
import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component'; import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component';
import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/interfaces/interfaces'; import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/interfaces/interfaces';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -34,6 +35,7 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in
templateUrl: './admin-market-data.html' templateUrl: './admin-market-data.html'
}) })
export class AdminMarketDataComponent implements OnDestroy, OnInit { export class AdminMarketDataComponent implements OnDestroy, OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public activeFilters: Filter[] = []; public activeFilters: Filter[] = [];
@ -75,6 +77,8 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
public filters$ = new Subject<Filter[]>(); public filters$ = new Subject<Filter[]>();
public isLoading = false; public isLoading = false;
public placeholder = ''; public placeholder = '';
public pageSize = DEFAULT_PAGE_SIZE;
public totalItems = 0;
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -82,7 +86,6 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
public constructor( public constructor(
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -117,34 +120,22 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
); );
} }
}); });
}
public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.filters$ this.filters$
.pipe( .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject))
distinctUntilChanged(), .subscribe((filters) => {
switchMap((filters) => {
this.isLoading = true;
this.activeFilters = filters; this.activeFilters = filters;
this.placeholder =
this.activeFilters.length <= 0 ? $localize`Filter by...` : '';
return this.dataService.fetchAdminMarketData({ this.loadData();
filters: this.activeFilters
}); });
}), }
takeUntil(this.unsubscribeSubject)
)
.subscribe(({ marketData }) => {
this.dataSource = new MatTableDataSource(marketData);
this.dataSource.sort = this.sort;
this.isLoading = false; public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
}
this.changeDetectorRef.markForCheck(); public onChangePage(page: PageEvent) {
}); this.loadData(page.pageIndex);
} }
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
@ -212,6 +203,35 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private loadData(aPageIndex = 0) {
this.isLoading = true;
if (aPageIndex === 0 && this.paginator) {
this.paginator.pageIndex = 0;
}
this.placeholder =
this.activeFilters.length <= 0 ? $localize`Filter by...` : '';
this.adminService
.fetchAdminMarketData({
filters: this.activeFilters,
skip: aPageIndex * this.pageSize,
take: this.pageSize
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ count, marketData }) => {
this.totalItems = count;
this.dataSource = new MatTableDataSource(marketData);
this.dataSource.sort = this.sort;
this.isLoading = false;
this.changeDetectorRef.markForCheck();
});
}
private openAssetProfileDialog({ private openAssetProfileDialog({
dataSource, dataSource,
symbol symbol
@ -274,8 +294,9 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
this.isLoading = true; this.isLoading = true;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
return this.dataService.fetchAdminMarketData({ return this.adminService.fetchAdminMarketData({
filters: this.activeFilters filters: this.activeFilters,
take: this.pageSize
}); });
}), }),
takeUntil(this.unsubscribeSubject) takeUntil(this.unsubscribeSubject)

38
apps/client/src/app/components/admin-market-data/admin-market-data.html

@ -29,7 +29,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="dataSource"> <ng-container matColumnDef="dataSource">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Data Source</ng-container> <ng-container i18n>Data Source</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@ -38,7 +38,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="assetClass"> <ng-container matColumnDef="assetClass">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Asset Class</ng-container> <ng-container i18n>Asset Class</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@ -47,7 +47,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="assetSubClass"> <ng-container matColumnDef="assetSubClass">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Asset Sub Class</ng-container> <ng-container i18n>Asset Sub Class</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@ -56,7 +56,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="date"> <ng-container matColumnDef="date">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>First Activity</ng-container> <ng-container i18n>First Activity</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@ -65,7 +65,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="activitiesCount"> <ng-container matColumnDef="activitiesCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Activities Count</ng-container> <ng-container i18n>Activities Count</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
@ -74,7 +74,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="marketDataItemCount"> <ng-container matColumnDef="marketDataItemCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Historical Data</ng-container> <ng-container i18n>Historical Data</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
@ -83,7 +83,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="sectorsCount"> <ng-container matColumnDef="sectorsCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Sectors Count</ng-container> <ng-container i18n>Sectors Count</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
@ -92,7 +92,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="countriesCount"> <ng-container matColumnDef="countriesCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Countries Count</ng-container> <ng-container i18n>Countries Count</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell> <td *matCellDef="let element" class="px-1 text-right" mat-cell>
@ -162,6 +162,28 @@
(click)="onOpenAssetProfileDialog({ dataSource: row.dataSource, symbol: row.symbol })" (click)="onOpenAssetProfileDialog({ dataSource: row.dataSource, symbol: row.symbol })"
></tr> ></tr>
</table> </table>
<mat-paginator
[length]="totalItems"
[ngClass]="{
'd-none':
(isLoading && totalItems === 0) ||
dataSource.data.length === 0
}"
[pageSize]="pageSize"
[showFirstLastButtons]="true"
(page)="onChangePage($event)"
></mat-paginator>
<ngx-skeleton-loader
*ngIf="isLoading && totalItems === 0"
animation="pulse"
class="px-4 py-3"
[theme]="{
height: '1.5rem',
width: '100%'
}"
></ngx-skeleton-loader>
</div> </div>
</div> </div>

4
apps/client/src/app/components/admin-market-data/admin-market-data.module.ts

@ -2,10 +2,12 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module'; import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AdminMarketDataComponent } from './admin-market-data.component'; import { AdminMarketDataComponent } from './admin-market-data.component';
import { GfAssetProfileDialogModule } from './asset-profile-dialog/asset-profile-dialog.module'; import { GfAssetProfileDialogModule } from './asset-profile-dialog/asset-profile-dialog.module';
@ -20,8 +22,10 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/
GfCreateAssetProfileDialogModule, GfCreateAssetProfileDialogModule,
MatButtonModule, MatButtonModule,
MatMenuModule, MatMenuModule,
MatPaginatorModule,
MatSortModule, MatSortModule,
MatTableModule, MatTableModule,
NgxSkeletonLoaderModule,
RouterModule RouterModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]

4
apps/client/src/app/components/admin-overview/admin-overview.component.ts

@ -1,5 +1,6 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatCheckboxChange } from '@angular/material/checkbox';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { CacheService } from '@ghostfolio/client/services/cache.service'; import { CacheService } from '@ghostfolio/client/services/cache.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
@ -45,6 +46,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private adminService: AdminService,
private cacheService: CacheService, private cacheService: CacheService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
@ -197,7 +199,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
} }
private fetchAdminData() { private fetchAdminData() {
this.dataService this.adminService
.fetchAdminData() .fetchAdminData()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ exchangeRates, settings, transactionCount, userCount }) => { .subscribe(({ exchangeRates, settings, transactionCount, userCount }) => {

4
apps/client/src/app/components/admin-users/admin-users.component.ts

@ -1,4 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
@ -30,6 +31,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
private impersonationStorageService: ImpersonationStorageService, private impersonationStorageService: ImpersonationStorageService,
@ -112,7 +114,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
} }
private fetchAdminData() { private fetchAdminData() {
this.dataService this.adminService
.fetchAdminData() .fetchAdminData()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ users }) => { .subscribe(({ users }) => {

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

@ -7,21 +7,28 @@ import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
AdminData,
AdminJobs, AdminJobs,
AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
EnhancedSymbolProfile, EnhancedSymbolProfile,
Filter,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { DataSource, MarketData, Platform } from '@prisma/client'; import { DataSource, MarketData, Platform } from '@prisma/client';
import { JobStatus } from 'bull'; import { JobStatus } from 'bull';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
import { Observable, map } from 'rxjs'; import { Observable, map } from 'rxjs';
import { DataService } from './data.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AdminService { export class AdminService {
public constructor(private http: HttpClient) {} public constructor(
private dataService: DataService,
private http: HttpClient
) {}
public addAssetProfile({ dataSource, symbol }: UniqueAsset) { public addAssetProfile({ dataSource, symbol }: UniqueAsset) {
return this.http.post<void>( return this.http.post<void>(
@ -56,6 +63,32 @@ export class AdminService {
); );
} }
public fetchAdminData() {
return this.http.get<AdminData>('/api/v1/admin');
}
public fetchAdminMarketData({
filters,
skip,
take
}: {
filters?: Filter[];
skip?: number;
take: number;
}) {
let params = this.dataService.buildFiltersAsQueryParams({ filters });
if (skip) {
params = params.append('skip', skip);
}
params = params.append('take', take);
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
params
});
}
public fetchAdminMarketDataBySymbol({ public fetchAdminMarketDataBySymbol({
dataSource, dataSource,
symbol symbol

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

@ -18,8 +18,6 @@ import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
Access, Access,
Accounts, Accounts,
AdminData,
AdminMarketData,
BenchmarkMarketDataDetails, BenchmarkMarketDataDetails,
BenchmarkResponse, BenchmarkResponse,
Export, Export,
@ -51,6 +49,67 @@ import { map } from 'rxjs/operators';
export class DataService { export class DataService {
public constructor(private http: HttpClient) {} public constructor(private http: HttpClient) {}
public buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) {
let params = new HttpParams();
if (filters?.length > 0) {
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
ASSET_SUB_CLASS: filtersByAssetSubClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
params = params.append(
'accounts',
filtersByAccount
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetSubClass) {
params = params.append(
'assetSubClasses',
filtersByAssetSubClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return params;
}
public createCheckoutSession({ public createCheckoutSession({
couponId, couponId,
priceId priceId
@ -92,16 +151,6 @@ export class DataService {
); );
} }
public fetchAdminData() {
return this.http.get<AdminData>('/api/v1/admin');
}
public fetchAdminMarketData({ filters }: { filters?: Filter[] }) {
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
params: this.buildFiltersAsQueryParams({ filters })
});
}
public fetchDividends({ public fetchDividends({
filters, filters,
groupBy = 'month', groupBy = 'month',
@ -450,65 +499,4 @@ export class DataService {
couponCode couponCode
}); });
} }
private buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) {
let params = new HttpParams();
if (filters?.length > 0) {
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
ASSET_SUB_CLASS: filtersByAssetSubClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
params = params.append(
'accounts',
filtersByAccount
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetSubClass) {
params = params.append(
'assetSubClasses',
filtersByAssetSubClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return params;
}
} }

1
libs/common/src/lib/interfaces/admin-market-data.interface.ts

@ -1,6 +1,7 @@
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
export interface AdminMarketData { export interface AdminMarketData {
count: number;
marketData: AdminMarketDataItem[]; marketData: AdminMarketDataItem[];
} }

2
libs/ui/src/lib/activities-table/activities-table.component.html

@ -562,7 +562,6 @@
</div> </div>
<mat-paginator <mat-paginator
showFirstLastButtons="true"
[ngClass]="{ [ngClass]="{
'd-none': 'd-none':
isLoading || isLoading ||
@ -570,6 +569,7 @@
dataSource.data.length <= pageSize dataSource.data.length <= pageSize
}" }"
[pageSize]="pageSize" [pageSize]="pageSize"
[showFirstLastButtons]="true"
(page)="onChangePage($event)" (page)="onChangePage($event)"
></mat-paginator> ></mat-paginator>

Loading…
Cancel
Save