diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ffbd6e7d8..66b99ba299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed the loading state in the user detail dialog of the admin control panel’s users section - Fixed a race condition where the portfolio snapshot computation was completed before its result had been cached, causing a redundant recomputation - Fixed an endless loop in the portfolio snapshot computation if the computed result could not be read from the cache diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts index 6521665e1b..5ffb756a0d 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts @@ -142,6 +142,8 @@ export class AssetProfilesController { }); return this.assetProfilesService.createSplit({ + dataSource, + symbol, symbolProfileId, date: parseISO(data.date), denominator: data.denominator, diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts index e3906be9ff..9df342f5f6 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts @@ -8,6 +8,7 @@ import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data- import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; +import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { Module } from '@nestjs/common'; @@ -23,6 +24,7 @@ import { AssetProfilesService } from './asset-profiles.service'; ApiModule, AssetProfileSplitModule, BenchmarkModule, + DataGatheringQueueModule, DataProviderModule, ExchangeRateDataModule, MarketDataModule, diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts index 7d75fc40bb..0d50e22233 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts @@ -5,6 +5,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { UpdateAssetProfileDataDto } from '@ghostfolio/common/dtos'; import { @@ -33,6 +34,7 @@ export class AssetProfilesService { private readonly activitiesService: ActivitiesService, private readonly assetProfileSplitService: AssetProfileSplitService, private readonly benchmarkService: BenchmarkService, + private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, @@ -41,22 +43,28 @@ export class AssetProfilesService { ) {} public async createSplit({ + dataSource, date, denominator, numerator, + symbol, symbolProfileId }: { date: Date; denominator: number; numerator: number; symbolProfileId: string; - }) { - return this.assetProfileSplitService.upsert({ + } & AssetProfileIdentifier) { + const assetProfileSplit = await this.assetProfileSplitService.upsert({ date, denominator, numerator, symbolProfileId }); + + await this.dataGatheringService.gatherSymbol({ dataSource, symbol }); + + return assetProfileSplit; } public async deleteSplit({ diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 7a1fc71dc8..2b679f34cc 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -1,7 +1,9 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { decodeDataSource } from '@ghostfolio/api/helper/data-source.helper'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; +import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; @@ -119,6 +121,7 @@ export class UserController { @Get() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getUser( @Headers('accept-language') acceptLanguage: string, @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string @@ -165,6 +168,7 @@ export class UserController { @Put('setting') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseInterceptors(TransformDataSourceInResponseInterceptor) public async updateUserSetting(@Body() data: UpdateUserSettingDto) { if ( size(data) === 1 && @@ -192,6 +196,12 @@ export class UserController { data ); + if (userSettings['filters.dataSource']) { + userSettings['filters.dataSource'] = decodeDataSource( + userSettings['filters.dataSource'] + ); + } + for (const key in userSettings) { if (userSettings[key] === false || userSettings[key] === null) { delete userSettings[key]; diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 3f4e898fc5..1566309647 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -1,6 +1,7 @@ import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; +import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; @@ -30,7 +31,8 @@ import { UserService } from './user.service'; PropertyModule, RedactValuesInResponseModule, SubscriptionModule, - TagModule + TagModule, + TransformDataSourceInResponseModule ], providers: [UserService] }) diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index 6b38b2d54b..81aabf4ae1 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -78,6 +78,7 @@ export class TransformDataSourceInResponseInterceptor< valueMap, object: data, paths: [ + '["filters.dataSource"]', 'activities[*].assetProfile.dataSource', 'activities[*].dataSource', 'assetProfile.dataSource', @@ -88,6 +89,7 @@ export class TransformDataSourceInResponseInterceptor< 'holdings[*].assetProfile.dataSource', 'holdings[*].dataSource', 'items[*].dataSource', + 'settings["filters.dataSource"]', 'watchlist[*].dataSource' ] }); diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index f5df34ab47..f80cec842a 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -514,10 +514,6 @@
-

- Splits are stored for this asset profile, but are not applied - to the portfolio calculation yet. -

@if (splits.length > 0) { @@ -580,38 +576,33 @@ - - Shares After - - Numerator, - e.g. 4 for a 4:1 split or 1 for a 1:10 reverse - split + - - - Shares Before - - Denominator, - e.g. 1 for a 4:1 split or 10 for a 1:10 reverse - splitShares After + + +
:
+ - + Shares Before + +
+
diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts index 9e341037a4..3aec4d42c7 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts @@ -56,14 +56,18 @@ export class GfUserDetailDialogComponent implements OnInit { protected readonly baseCurrency: string; protected readonly canDeleteUser = canDeleteUser; protected readonly getCountryName = getCountryName; + protected isLoading = true; + protected readonly subscriptionsDataSource = new MatTableDataSource(); + protected readonly subscriptionsDisplayedColumns = [ 'createdAt', 'type', 'price', 'expiresAt' ]; + protected user: AdminUserResponse; protected readonly data = inject(MAT_DIALOG_DATA); @@ -72,6 +76,7 @@ export class GfUserDetailDialogComponent implements OnInit { private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly dataService = inject(DataService); private readonly destroyRef = inject(DestroyRef); + private readonly dialogRef = inject>( MatDialogRef @@ -101,6 +106,8 @@ export class GfUserDetailDialogComponent implements OnInit { this.subscriptionsDataSource.data = this.user.subscriptions ?? []; + this.isLoading = false; + this.changeDetectorRef.markForCheck(); }); } diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html index e2cdb66a56..b77e0043e8 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -36,12 +36,14 @@ i18n size="medium" [enableCopyToClipboardButton]="true" - [value]="user?.id" + [value]="isLoading ? undefined : user.id" >User ID
- Role + Role
@@ -52,7 +54,7 @@ size="medium" [isDate]="true" [locale]="data.locale" - [value]="user?.createdAt" + [value]="isLoading ? undefined : user.createdAt" >Registration Date @@ -61,7 +63,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.provider" + [value]="isLoading ? undefined : user.provider" >Authentication @@ -74,7 +76,9 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.subscription ? 'Premium' : 'Basic'" + [value]=" + isLoading ? undefined : user.subscription ? 'Premium' : 'Basic' + " >Membership @@ -83,7 +87,11 @@ i18n size="medium" [value]=" - user?.country ? getCountryName({ code: user.country }) : '-' + isLoading + ? undefined + : user.country + ? getCountryName({ code: user.country }) + : '-' " >Country @@ -97,7 +105,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.accountCount" + [value]="isLoading ? undefined : user.accountCount" >Accounts @@ -106,7 +114,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.activityCount" + [value]="isLoading ? undefined : user.activityCount" >Activities @@ -120,7 +128,7 @@ size="medium" [locale]="data.locale" [precision]="0" - [value]="user?.engagement" + [value]="isLoading ? undefined : user.engagement" >Engagement per Day @@ -129,7 +137,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.dailyApiRequests" + [value]="isLoading ? undefined : user.dailyApiRequests" >API Requests Today