Browse Source

Merge branch 'main' into italiantranslation1

pull/3744/head^2
Thomas Kaul 12 months ago
committed by GitHub
parent
commit
d9745ae0e3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 8
      apps/api/src/app/admin/admin.controller.ts
  3. 10
      apps/api/src/app/admin/admin.service.ts
  4. 28
      apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
  5. 5
      apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
  6. 10
      apps/client/src/app/components/admin-jobs/admin-jobs.html
  7. 2
      apps/client/src/app/components/admin-jobs/admin-jobs.module.ts
  8. 17
      apps/client/src/app/components/admin-users/admin-users.component.ts
  9. 10
      apps/client/src/app/components/admin-users/admin-users.html
  10. 4
      apps/client/src/app/components/admin-users/admin-users.module.ts
  11. 5
      apps/client/src/app/services/admin.service.ts
  12. 12
      libs/common/src/lib/interfaces/admin-data.interface.ts
  13. 14
      libs/common/src/lib/interfaces/admin-users.interface.ts
  14. 2
      libs/common/src/lib/interfaces/index.ts
  15. 2
      package.json

6
CHANGELOG.md

@ -9,13 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Extracted the users from the admin control panel endpoint to a dedicated endpoint
- Improved the language localization for Italian (`it`)
## 2.106.0-beta.6
## 2.106.0 - 2024-09-07
### 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
@ -38,6 +41,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

8
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<AdminUsers> {
return this.adminService.getUsers();
}
}

10
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<AdminUsers> {
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<AdminData['users']> {
private async getUsersWithAnalytics(): Promise<AdminUsers['users']> {
let orderBy: any = {
createdAt: 'desc'
};
let where;
let where: Prisma.UserWhereInput;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
orderBy = {

28
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,7 +65,8 @@ export class YahooFinanceService implements DataProviderInterface {
}
try {
const historicalResult = await yahooFinance.historical(
const historicalResult = this.convertToDividendResult(
await yahooFinance.chart(
this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
symbol
),
@ -70,8 +76,8 @@ export class YahooFinanceService implements DataProviderInterface {
period1: format(from, DATE_FORMAT),
period2: format(to, DATE_FORMAT)
}
)
);
const response: {
[date: string]: IDataProviderHistoricalResponse;
} = {};
@ -108,7 +114,8 @@ export class YahooFinanceService implements DataProviderInterface {
}
try {
const historicalResult = await yahooFinance.historical(
const historicalResult = this.convertToHistoricalResult(
await yahooFinance.chart(
this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
symbol
),
@ -117,6 +124,7 @@ export class YahooFinanceService implements DataProviderInterface {
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(() => {

5
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();
});
}

10
apps/client/src/app/components/admin-jobs/admin-jobs.html

@ -183,6 +183,16 @@
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
</table>
@if (isLoading) {
<ngx-skeleton-loader
animation="pulse"
class="px-4 py-3"
[theme]="{
height: '1.5rem',
width: '100%'
}"
/>
}
</div>
</div>
</div>

2
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]

17
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<AdminData['users'][0]> =
public dataSource: MatTableDataSource<AdminUsers['users'][0]> =
new MatTableDataSource();
public defaultDateFormat: string;
public displayedColumns: string[] = [];
@ -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<void>();
@ -93,7 +94,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
this.fetchAdminData();
this.fetchUsers();
}
public formatDistanceToNow(aDateString: string) {
@ -118,7 +119,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
.deleteUser(aId)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.fetchAdminData();
this.fetchUsers();
});
},
confirmType: ConfirmationDialogType.Warn,
@ -141,13 +142,17 @@ 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);
this.isLoading = false;
this.changeDetectorRef.markForCheck();
});
}

10
apps/client/src/app/components/admin-users/admin-users.html

@ -245,6 +245,16 @@
></tr>
</table>
</div>
@if (isLoading) {
<ngx-skeleton-loader
animation="pulse"
class="px-4 py-3"
[theme]="{
height: '1.5rem',
width: '100%'
}"
/>
}
</div>
</div>
</div>

4
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]
})

5
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<Tag[]>('/api/v1/tag');
}
public fetchUsers() {
return this.http.get<AdminUsers>('/api/v1/admin/user');
}
public gather7Days() {
return this.http.post<void>('/api/v1/admin/gather', {});
}

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

14
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;
}[];
}

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

2
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",

Loading…
Cancel
Save