Browse Source

Merge branch 'italiantranslation1' of https://github.com/sghebuz/ghostfolio into pr/3744

pull/3744/head
Thomas Kaul 12 months ago
parent
commit
922ee4dc09
  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 ### Changed
- Extracted the users from the admin control panel endpoint to a dedicated endpoint
- Improved the language localization for Italian (`it`) - Improved the language localization for Italian (`it`)
## 2.106.0-beta.6 ## 2.106.0 - 2024-09-07
### Added ### Added
- Set up a performance logging service - 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 - Added the attribute `mode` to the scraper configuration to get quotes instantly
### Changed ### 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 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 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 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 - 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 ## 2.105.0 - 2024-08-21

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

@ -17,6 +17,7 @@ import {
AdminData, AdminData,
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminUsers,
EnhancedSymbolProfile EnhancedSymbolProfile
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
@ -347,4 +348,11 @@ export class AdminController {
) { ) {
return this.adminService.putSetting(key, data.value); 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, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem, AdminMarketDataItem,
AdminUsers,
AssetProfileIdentifier, AssetProfileIdentifier,
EnhancedSymbolProfile, EnhancedSymbolProfile,
Filter Filter
@ -135,7 +136,6 @@ export class AdminService {
settings: await this.propertyService.get(), settings: await this.propertyService.get(),
transactionCount: await this.prismaService.order.count(), transactionCount: await this.prismaService.order.count(),
userCount: await this.prismaService.user.count(), userCount: await this.prismaService.user.count(),
users: await this.getUsersWithAnalytics(),
version: environment.version version: environment.version
}; };
} }
@ -377,6 +377,10 @@ export class AdminService {
}; };
} }
public async getUsers(): Promise<AdminUsers> {
return { users: await this.getUsersWithAnalytics() };
}
public async patchAssetProfileData({ public async patchAssetProfileData({
assetClass, assetClass,
assetSubClass, assetSubClass,
@ -546,11 +550,11 @@ export class AdminService {
return { marketData, count: marketData.length }; return { marketData, count: marketData.length };
} }
private async getUsersWithAnalytics(): Promise<AdminData['users']> { private async getUsersWithAnalytics(): Promise<AdminUsers['users']> {
let orderBy: any = { let orderBy: any = {
createdAt: 'desc' createdAt: 'desc'
}; };
let where; let where: Prisma.UserWhereInput;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
orderBy = { 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 { DataSource, SymbolProfile } from '@prisma/client';
import { addDays, format, isSameDay } from 'date-fns'; import { addDays, format, isSameDay } from 'date-fns';
import yahooFinance from 'yahoo-finance2'; 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'; import { Quote } from 'yahoo-finance2/dist/esm/src/modules/quote';
@Injectable() @Injectable()
@ -60,7 +65,8 @@ export class YahooFinanceService implements DataProviderInterface {
} }
try { try {
const historicalResult = await yahooFinance.historical( const historicalResult = this.convertToDividendResult(
await yahooFinance.chart(
this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol( this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
symbol symbol
), ),
@ -70,8 +76,8 @@ export class YahooFinanceService implements DataProviderInterface {
period1: format(from, DATE_FORMAT), period1: format(from, DATE_FORMAT),
period2: format(to, DATE_FORMAT) period2: format(to, DATE_FORMAT)
} }
)
); );
const response: { const response: {
[date: string]: IDataProviderHistoricalResponse; [date: string]: IDataProviderHistoricalResponse;
} = {}; } = {};
@ -108,7 +114,8 @@ export class YahooFinanceService implements DataProviderInterface {
} }
try { try {
const historicalResult = await yahooFinance.historical( const historicalResult = this.convertToHistoricalResult(
await yahooFinance.chart(
this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol( this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(
symbol symbol
), ),
@ -117,6 +124,7 @@ export class YahooFinanceService implements DataProviderInterface {
period1: format(from, DATE_FORMAT), period1: format(from, DATE_FORMAT),
period2: format(to, DATE_FORMAT) period2: format(to, DATE_FORMAT)
} }
)
); );
const response: { const response: {
@ -302,6 +310,20 @@ export class YahooFinanceService implements DataProviderInterface {
return { items }; 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[]) { private async getQuotesWithQuoteSummary(aYahooFinanceSymbols: string[]) {
const quoteSummaryPromises = aYahooFinanceSymbols.map((symbol) => { const quoteSummaryPromises = aYahooFinanceSymbols.map((symbol) => {
return yahooFinance.quoteSummary(symbol).catch(() => { 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', 'status',
'actions' 'actions'
]; ];
public isLoading = false;
public statusFilterOptions = QUEUE_JOB_STATUS_LIST; public statusFilterOptions = QUEUE_JOB_STATUS_LIST;
public user: User; public user: User;
@ -138,12 +139,16 @@ export class AdminJobsComponent implements OnDestroy, OnInit {
} }
private fetchJobs(aStatus?: JobStatus[]) { private fetchJobs(aStatus?: JobStatus[]) {
this.isLoading = true;
this.adminService this.adminService
.fetchJobs({ status: aStatus }) .fetchJobs({ status: aStatus })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ jobs }) => { .subscribe(({ jobs }) => {
this.dataSource = new MatTableDataSource(jobs); this.dataSource = new MatTableDataSource(jobs);
this.isLoading = false;
this.changeDetectorRef.markForCheck(); 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 *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr> <tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
</table> </table>
@if (isLoading) {
<ngx-skeleton-loader
animation="pulse"
class="px-4 py-3"
[theme]="{
height: '1.5rem',
width: '100%'
}"
/>
}
</div> </div>
</div> </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 { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AdminJobsComponent } from './admin-jobs.component'; import { AdminJobsComponent } from './admin-jobs.component';
@ -17,6 +18,7 @@ import { AdminJobsComponent } from './admin-jobs.component';
MatMenuModule, MatMenuModule,
MatSelectModule, MatSelectModule,
MatTableModule, MatTableModule,
NgxSkeletonLoaderModule,
ReactiveFormsModule ReactiveFormsModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] 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 { 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';
import { getDateFormatString, getEmojiFlag } from '@ghostfolio/common/helper'; 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 { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
@ -24,7 +24,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-users.html' templateUrl: './admin-users.html'
}) })
export class AdminUsersComponent implements OnDestroy, OnInit { export class AdminUsersComponent implements OnDestroy, OnInit {
public dataSource: MatTableDataSource<AdminData['users'][0]> = public dataSource: MatTableDataSource<AdminUsers['users'][0]> =
new MatTableDataSource(); new MatTableDataSource();
public defaultDateFormat: string; public defaultDateFormat: string;
public displayedColumns: string[] = []; public displayedColumns: string[] = [];
@ -32,6 +32,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
public hasPermissionForSubscription: boolean; public hasPermissionForSubscription: boolean;
public hasPermissionToImpersonateAllUsers: boolean; public hasPermissionToImpersonateAllUsers: boolean;
public info: InfoItem; public info: InfoItem;
public isLoading = false;
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -93,7 +94,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.fetchAdminData(); this.fetchUsers();
} }
public formatDistanceToNow(aDateString: string) { public formatDistanceToNow(aDateString: string) {
@ -118,7 +119,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
.deleteUser(aId) .deleteUser(aId)
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
this.fetchAdminData(); this.fetchUsers();
}); });
}, },
confirmType: ConfirmationDialogType.Warn, confirmType: ConfirmationDialogType.Warn,
@ -141,13 +142,17 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private fetchAdminData() { private fetchUsers() {
this.isLoading = true;
this.adminService this.adminService
.fetchAdminData() .fetchUsers()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ users }) => { .subscribe(({ users }) => {
this.dataSource = new MatTableDataSource(users); this.dataSource = new MatTableDataSource(users);
this.isLoading = false;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }

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

@ -245,6 +245,16 @@
></tr> ></tr>
</table> </table>
</div> </div>
@if (isLoading) {
<ngx-skeleton-loader
animation="pulse"
class="px-4 py-3"
[theme]="{
height: '1.5rem',
width: '100%'
}"
/>
}
</div> </div>
</div> </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 { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AdminUsersComponent } from './admin-users.component'; import { AdminUsersComponent } from './admin-users.component';
@ -18,7 +19,8 @@ import { AdminUsersComponent } from './admin-users.component';
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,
MatMenuModule, MatMenuModule,
MatTableModule MatTableModule,
NgxSkeletonLoaderModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })

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

@ -12,6 +12,7 @@ import {
AdminJobs, AdminJobs,
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminUsers,
EnhancedSymbolProfile, EnhancedSymbolProfile,
Filter Filter
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
@ -155,6 +156,10 @@ export class AdminService {
return this.http.get<Tag[]>('/api/v1/tag'); return this.http.get<Tag[]>('/api/v1/tag');
} }
public fetchUsers() {
return this.http.get<AdminUsers>('/api/v1/admin/user');
}
public gather7Days() { public gather7Days() {
return this.http.post<void>('/api/v1/admin/gather', {}); 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 { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { Role } from '@prisma/client';
export interface AdminData { export interface AdminData {
exchangeRates: ({ exchangeRates: ({
label1: string; label1: string;
@ -11,15 +9,5 @@ export interface AdminData {
settings: { [key: string]: boolean | object | string | string[] }; settings: { [key: string]: boolean | object | string | string[] };
transactionCount: number; transactionCount: number;
userCount: number; userCount: number;
users: {
accountCount: number;
country: string;
createdAt: Date;
engagement: number;
id: string;
lastActivity: Date;
role: Role;
transactionCount: number;
}[];
version: string; 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, AdminMarketData,
AdminMarketDataItem AdminMarketDataItem
} from './admin-market-data.interface'; } from './admin-market-data.interface';
import type { AdminUsers } from './admin-users.interface';
import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { AssetProfileIdentifier } from './asset-profile-identifier.interface';
import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface'; import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
import type { BenchmarkProperty } from './benchmark-property.interface'; import type { BenchmarkProperty } from './benchmark-property.interface';
@ -61,6 +62,7 @@ export {
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem, AdminMarketDataItem,
AdminUsers,
AssetProfileIdentifier, AssetProfileIdentifier,
Benchmark, Benchmark,
BenchmarkMarketDataDetails, BenchmarkMarketDataDetails,

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.106.0-beta.6", "version": "2.106.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",

Loading…
Cancel
Save