From 8c322b4e817075ff2510324aaa07ad8b53b8cd55 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 7 Sep 2024 21:21:02 +0200
Subject: [PATCH 1/4] Bugfix/fix historical market data gathering in yahoo
finance service (#3737)
* Switch from historical() to chart()
* Update changelog
---
CHANGELOG.md | 1 +
.../yahoo-finance/yahoo-finance.service.ts | 62 +++++++++++++------
2 files changed, 43 insertions(+), 20 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1bb6525f..5f1102d92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed an issue in the view mode toggle of the holdings tab on the home page (experimental)
- Fixed an issue on the portfolio activities page by loading the data only once
- Fixed an issue in the carousel component for the testimonial section on the landing page
+- Fixed the historical market data gathering in the _Yahoo Finance_ service by switching from `historical()` to `chart()`
- Handled an exception in the historical market data component of the asset profile details dialog in the admin control panel
## 2.105.0 - 2024-08-21
diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
index e0d88f0c6..a8f7d261e 100644
--- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
+++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
@@ -20,6 +20,11 @@ import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { addDays, format, isSameDay } from 'date-fns';
import yahooFinance from 'yahoo-finance2';
+import { ChartResultArray } from 'yahoo-finance2/dist/esm/src/modules/chart';
+import {
+ HistoricalDividendsResult,
+ HistoricalHistoryResult
+} from 'yahoo-finance2/dist/esm/src/modules/historical';
import { Quote } from 'yahoo-finance2/dist/esm/src/modules/quote';
@Injectable()
@@ -60,18 +65,19 @@ export class YahooFinanceService implements DataProviderInterface {
}
try {
- const historicalResult = await yahooFinance.historical(
- this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
- symbol
- ),
- {
- events: 'dividends',
- interval: granularity === 'month' ? '1mo' : '1d',
- period1: format(from, DATE_FORMAT),
- period2: format(to, DATE_FORMAT)
- }
+ const historicalResult = this.convertToDividendResult(
+ await yahooFinance.chart(
+ this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
+ symbol
+ ),
+ {
+ events: 'dividends',
+ interval: granularity === 'month' ? '1mo' : '1d',
+ period1: format(from, DATE_FORMAT),
+ period2: format(to, DATE_FORMAT)
+ }
+ )
);
-
const response: {
[date: string]: IDataProviderHistoricalResponse;
} = {};
@@ -108,15 +114,17 @@ export class YahooFinanceService implements DataProviderInterface {
}
try {
- const historicalResult = await yahooFinance.historical(
- this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
- symbol
- ),
- {
- interval: '1d',
- period1: format(from, DATE_FORMAT),
- period2: format(to, DATE_FORMAT)
- }
+ const historicalResult = this.convertToHistoricalResult(
+ await yahooFinance.chart(
+ this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
+ symbol
+ ),
+ {
+ interval: '1d',
+ period1: format(from, DATE_FORMAT),
+ period2: format(to, DATE_FORMAT)
+ }
+ )
);
const response: {
@@ -302,6 +310,20 @@ export class YahooFinanceService implements DataProviderInterface {
return { items };
}
+ private convertToDividendResult(
+ result: ChartResultArray
+ ): HistoricalDividendsResult {
+ return result.events.dividends.map(({ amount: dividends, date }) => {
+ return { date, dividends };
+ });
+ }
+
+ private convertToHistoricalResult(
+ result: ChartResultArray
+ ): HistoricalHistoryResult {
+ return result.quotes;
+ }
+
private async getQuotesWithQuoteSummary(aYahooFinanceSymbols: string[]) {
const quoteSummaryPromises = aYahooFinanceSymbols.map((symbol) => {
return yahooFinance.quoteSummary(symbol).catch(() => {
From 1bc2b474527ff625db443f2ddb5f56af4b25aeb2 Mon Sep 17 00:00:00 2001
From: Shaunak Das <51281688+shaun-ak@users.noreply.github.com>
Date: Sun, 8 Sep 2024 00:52:56 +0530
Subject: [PATCH 2/4] Feature/setup skeleton loader for data tables (#3735)
* Setup skeleton loader for data tables
* Update changelog
---
CHANGELOG.md | 2 ++
.../app/components/admin-jobs/admin-jobs.component.ts | 5 +++++
.../src/app/components/admin-jobs/admin-jobs.html | 10 ++++++++++
.../src/app/components/admin-jobs/admin-jobs.module.ts | 2 ++
.../components/admin-users/admin-users.component.ts | 5 +++++
.../src/app/components/admin-users/admin-users.html | 10 ++++++++++
.../app/components/admin-users/admin-users.module.ts | 4 +++-
7 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f1102d92..56ac6210d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Set up a performance logging service
+- Added a loading indicator to the queue jobs table in the admin control panel
+- Added a loading indicator to the users table in the admin control panel
- Added the attribute `mode` to the scraper configuration to get quotes instantly
### Changed
diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
index 820b3651d..e828049bc 100644
--- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
+++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
@@ -51,6 +51,7 @@ export class AdminJobsComponent implements OnDestroy, OnInit {
'status',
'actions'
];
+ public isLoading = false;
public statusFilterOptions = QUEUE_JOB_STATUS_LIST;
public user: User;
@@ -138,12 +139,16 @@ export class AdminJobsComponent implements OnDestroy, OnInit {
}
private fetchJobs(aStatus?: JobStatus[]) {
+ this.isLoading = true;
+
this.adminService
.fetchJobs({ status: aStatus })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ jobs }) => {
this.dataSource = new MatTableDataSource(jobs);
+ this.isLoading = false;
+
this.changeDetectorRef.markForCheck();
});
}
diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.html b/apps/client/src/app/components/admin-jobs/admin-jobs.html
index 9ea2097e2..e194b2b37 100644
--- a/apps/client/src/app/components/admin-jobs/admin-jobs.html
+++ b/apps/client/src/app/components/admin-jobs/admin-jobs.html
@@ -183,6 +183,16 @@
+ @if (isLoading) {
+
+ }
diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.module.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.module.ts
index fe717b904..cca66a04a 100644
--- a/apps/client/src/app/components/admin-jobs/admin-jobs.module.ts
+++ b/apps/client/src/app/components/admin-jobs/admin-jobs.module.ts
@@ -5,6 +5,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AdminJobsComponent } from './admin-jobs.component';
@@ -17,6 +18,7 @@ import { AdminJobsComponent } from './admin-jobs.component';
MatMenuModule,
MatSelectModule,
MatTableModule,
+ NgxSkeletonLoaderModule,
ReactiveFormsModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts
index 0a66977bf..4a419dd6c 100644
--- a/apps/client/src/app/components/admin-users/admin-users.component.ts
+++ b/apps/client/src/app/components/admin-users/admin-users.component.ts
@@ -32,6 +32,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
public hasPermissionForSubscription: boolean;
public hasPermissionToImpersonateAllUsers: boolean;
public info: InfoItem;
+ public isLoading = false;
public user: User;
private unsubscribeSubject = new Subject();
@@ -142,12 +143,16 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
}
private fetchAdminData() {
+ this.isLoading = true;
+
this.adminService
.fetchAdminData()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ users }) => {
this.dataSource = new MatTableDataSource(users);
+ this.isLoading = false;
+
this.changeDetectorRef.markForCheck();
});
}
diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html
index 25ab9053d..b65b7c821 100644
--- a/apps/client/src/app/components/admin-users/admin-users.html
+++ b/apps/client/src/app/components/admin-users/admin-users.html
@@ -245,6 +245,16 @@
>
+ @if (isLoading) {
+
+ }
diff --git a/apps/client/src/app/components/admin-users/admin-users.module.ts b/apps/client/src/app/components/admin-users/admin-users.module.ts
index 3f4e9f2f7..fcf25c8b5 100644
--- a/apps/client/src/app/components/admin-users/admin-users.module.ts
+++ b/apps/client/src/app/components/admin-users/admin-users.module.ts
@@ -6,6 +6,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatTableModule } from '@angular/material/table';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AdminUsersComponent } from './admin-users.component';
@@ -18,7 +19,8 @@ import { AdminUsersComponent } from './admin-users.component';
GfValueComponent,
MatButtonModule,
MatMenuModule,
- MatTableModule
+ MatTableModule,
+ NgxSkeletonLoaderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
From 728f84e7eb960a81417940ed4bfd6f4034f4a699 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 7 Sep 2024 21:24:33 +0200
Subject: [PATCH 3/4] Release 2.106.0 (#3738)
---
CHANGELOG.md | 2 +-
package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56ac6210d..f91b3b62e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## 2.106.0-beta.6
+## 2.106.0 - 2024-09-07
### Added
diff --git a/package.json b/package.json
index 79ed8f8c1..81eb3859f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ghostfolio",
- "version": "2.106.0-beta.6",
+ "version": "2.106.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
From 9cd4321bd02c2f3cb6eaf7010c020882937a0d84 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 8 Sep 2024 09:56:08 +0200
Subject: [PATCH 4/4] Feature/extract users from admin control panel endpoint
to dedicated endpoint (#3740)
* Introduce GET api/v1/admin/user endpoint
* Update changelog
---
CHANGELOG.md | 6 ++++++
apps/api/src/app/admin/admin.controller.ts | 8 ++++++++
apps/api/src/app/admin/admin.service.ts | 10 +++++++---
.../admin-users/admin-users.component.ts | 12 ++++++------
apps/client/src/app/services/admin.service.ts | 5 +++++
.../src/lib/interfaces/admin-data.interface.ts | 12 ------------
.../src/lib/interfaces/admin-users.interface.ts | 14 ++++++++++++++
libs/common/src/lib/interfaces/index.ts | 2 ++
8 files changed, 48 insertions(+), 21 deletions(-)
create mode 100644 libs/common/src/lib/interfaces/admin-users.interface.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f91b3b62e..eae074550 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### Changed
+
+- Extracted the users from the admin control panel endpoint to a dedicated endpoint
+
## 2.106.0 - 2024-09-07
### Added
diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts
index 6d201be23..da4b5dd7e 100644
--- a/apps/api/src/app/admin/admin.controller.ts
+++ b/apps/api/src/app/admin/admin.controller.ts
@@ -17,6 +17,7 @@ import {
AdminData,
AdminMarketData,
AdminMarketDataDetails,
+ AdminUsers,
EnhancedSymbolProfile
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
@@ -347,4 +348,11 @@ export class AdminController {
) {
return this.adminService.putSetting(key, data.value);
}
+
+ @Get('user')
+ @HasPermission(permissions.accessAdminControl)
+ @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
+ public async getUsers(): Promise {
+ return this.adminService.getUsers();
+ }
}
diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts
index 50b781f54..3f5274285 100644
--- a/apps/api/src/app/admin/admin.service.ts
+++ b/apps/api/src/app/admin/admin.service.ts
@@ -21,6 +21,7 @@ import {
AdminMarketData,
AdminMarketDataDetails,
AdminMarketDataItem,
+ AdminUsers,
AssetProfileIdentifier,
EnhancedSymbolProfile,
Filter
@@ -135,7 +136,6 @@ export class AdminService {
settings: await this.propertyService.get(),
transactionCount: await this.prismaService.order.count(),
userCount: await this.prismaService.user.count(),
- users: await this.getUsersWithAnalytics(),
version: environment.version
};
}
@@ -377,6 +377,10 @@ export class AdminService {
};
}
+ public async getUsers(): Promise {
+ return { users: await this.getUsersWithAnalytics() };
+ }
+
public async patchAssetProfileData({
assetClass,
assetSubClass,
@@ -546,11 +550,11 @@ export class AdminService {
return { marketData, count: marketData.length };
}
- private async getUsersWithAnalytics(): Promise {
+ private async getUsersWithAnalytics(): Promise {
let orderBy: any = {
createdAt: 'desc'
};
- let where;
+ let where: Prisma.UserWhereInput;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
orderBy = {
diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts
index 4a419dd6c..c5264c3b3 100644
--- a/apps/client/src/app/components/admin-users/admin-users.component.ts
+++ b/apps/client/src/app/components/admin-users/admin-users.component.ts
@@ -5,7 +5,7 @@ import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { getDateFormatString, getEmojiFlag } from '@ghostfolio/common/helper';
-import { AdminData, InfoItem, User } from '@ghostfolio/common/interfaces';
+import { AdminUsers, InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
@@ -24,7 +24,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-users.html'
})
export class AdminUsersComponent implements OnDestroy, OnInit {
- public dataSource: MatTableDataSource =
+ public dataSource: MatTableDataSource =
new MatTableDataSource();
public defaultDateFormat: string;
public displayedColumns: string[] = [];
@@ -94,7 +94,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
- this.fetchAdminData();
+ this.fetchUsers();
}
public formatDistanceToNow(aDateString: string) {
@@ -119,7 +119,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
.deleteUser(aId)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
- this.fetchAdminData();
+ this.fetchUsers();
});
},
confirmType: ConfirmationDialogType.Warn,
@@ -142,11 +142,11 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete();
}
- private fetchAdminData() {
+ private fetchUsers() {
this.isLoading = true;
this.adminService
- .fetchAdminData()
+ .fetchUsers()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ users }) => {
this.dataSource = new MatTableDataSource(users);
diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts
index e5ea176d1..4c011e8c1 100644
--- a/apps/client/src/app/services/admin.service.ts
+++ b/apps/client/src/app/services/admin.service.ts
@@ -12,6 +12,7 @@ import {
AdminJobs,
AdminMarketData,
AdminMarketDataDetails,
+ AdminUsers,
EnhancedSymbolProfile,
Filter
} from '@ghostfolio/common/interfaces';
@@ -155,6 +156,10 @@ export class AdminService {
return this.http.get('/api/v1/tag');
}
+ public fetchUsers() {
+ return this.http.get('/api/v1/admin/user');
+ }
+
public gather7Days() {
return this.http.post('/api/v1/admin/gather', {});
}
diff --git a/libs/common/src/lib/interfaces/admin-data.interface.ts b/libs/common/src/lib/interfaces/admin-data.interface.ts
index 6b139026b..3dc476df8 100644
--- a/libs/common/src/lib/interfaces/admin-data.interface.ts
+++ b/libs/common/src/lib/interfaces/admin-data.interface.ts
@@ -1,7 +1,5 @@
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
-import { Role } from '@prisma/client';
-
export interface AdminData {
exchangeRates: ({
label1: string;
@@ -11,15 +9,5 @@ export interface AdminData {
settings: { [key: string]: boolean | object | string | string[] };
transactionCount: number;
userCount: number;
- users: {
- accountCount: number;
- country: string;
- createdAt: Date;
- engagement: number;
- id: string;
- lastActivity: Date;
- role: Role;
- transactionCount: number;
- }[];
version: string;
}
diff --git a/libs/common/src/lib/interfaces/admin-users.interface.ts b/libs/common/src/lib/interfaces/admin-users.interface.ts
new file mode 100644
index 000000000..24eb45c85
--- /dev/null
+++ b/libs/common/src/lib/interfaces/admin-users.interface.ts
@@ -0,0 +1,14 @@
+import { Role } from '@prisma/client';
+
+export interface AdminUsers {
+ users: {
+ accountCount: number;
+ country: string;
+ createdAt: Date;
+ engagement: number;
+ id: string;
+ lastActivity: Date;
+ role: Role;
+ transactionCount: number;
+ }[];
+}
diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts
index f7224407b..efab780fd 100644
--- a/libs/common/src/lib/interfaces/index.ts
+++ b/libs/common/src/lib/interfaces/index.ts
@@ -7,6 +7,7 @@ import type {
AdminMarketData,
AdminMarketDataItem
} from './admin-market-data.interface';
+import type { AdminUsers } from './admin-users.interface';
import type { AssetProfileIdentifier } from './asset-profile-identifier.interface';
import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
import type { BenchmarkProperty } from './benchmark-property.interface';
@@ -61,6 +62,7 @@ export {
AdminMarketData,
AdminMarketDataDetails,
AdminMarketDataItem,
+ AdminUsers,
AssetProfileIdentifier,
Benchmark,
BenchmarkMarketDataDetails,