Browse Source

Feature/improve watchlist for impersonation mode (#4632)

* Improve watchlist for impersonation mode

* Update changelog
pull/4643/head
Thomas Kaul 5 days ago
committed by GitHub
parent
commit
f70d71d5bd
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 13
      apps/api/src/app/endpoints/watchlist/watchlist.controller.ts
  3. 2
      apps/api/src/app/endpoints/watchlist/watchlist.module.ts
  4. 30
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  5. 2
      apps/client/src/app/components/home-watchlist/home-watchlist.html

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Extended the watchlist by the date of the last all time high, the current change to the all time high and the current market condition (experimental) - Extended the watchlist by the date of the last all time high, the current change to the all time high and the current market condition (experimental)
- Added support for the impersonation mode in the watchlist (experimental)
### Changed ### Changed

13
apps/api/src/app/endpoints/watchlist/watchlist.controller.ts

@ -2,6 +2,8 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { WatchlistResponse } from '@ghostfolio/common/interfaces'; import { WatchlistResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import { RequestWithUser } from '@ghostfolio/common/types';
@ -11,6 +13,7 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
Headers,
HttpException, HttpException,
Inject, Inject,
Param, Param,
@ -29,6 +32,7 @@ import { WatchlistService } from './watchlist.service';
@Controller('watchlist') @Controller('watchlist')
export class WatchlistController { export class WatchlistController {
public constructor( public constructor(
private readonly impersonationService: ImpersonationService,
@Inject(REQUEST) private readonly request: RequestWithUser, @Inject(REQUEST) private readonly request: RequestWithUser,
private readonly watchlistService: WatchlistService private readonly watchlistService: WatchlistService
) {} ) {}
@ -79,9 +83,14 @@ export class WatchlistController {
@HasPermission(permissions.readWatchlist) @HasPermission(permissions.readWatchlist)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getWatchlistItems(): Promise<WatchlistResponse> { public async getWatchlistItems(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string
): Promise<WatchlistResponse> {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId);
const watchlist = await this.watchlistService.getWatchlistItems( const watchlist = await this.watchlistService.getWatchlistItems(
this.request.user.id impersonationUserId || this.request.user.id
); );
return { return {

2
apps/api/src/app/endpoints/watchlist/watchlist.module.ts

@ -2,6 +2,7 @@ import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module'; import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
@ -18,6 +19,7 @@ import { WatchlistService } from './watchlist.service';
BenchmarkModule, BenchmarkModule,
DataGatheringModule, DataGatheringModule,
DataProviderModule, DataProviderModule,
ImpersonationModule,
MarketDataModule, MarketDataModule,
PrismaModule, PrismaModule,
SymbolProfileModule, SymbolProfileModule,

30
apps/client/src/app/components/home-watchlist/home-watchlist.component.ts

@ -1,4 +1,5 @@
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 { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
@ -45,6 +46,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
}) })
export class HomeWatchlistComponent implements OnDestroy, OnInit { export class HomeWatchlistComponent implements OnDestroy, OnInit {
public deviceType: string; public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToCreateWatchlistItem: boolean; public hasPermissionToCreateWatchlistItem: boolean;
public hasPermissionToDeleteWatchlistItem: boolean; public hasPermissionToDeleteWatchlistItem: boolean;
public user: User; public user: User;
@ -57,12 +59,20 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private impersonationStorageService: ImpersonationStorageService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private userService: UserService private userService: UserService
) { ) {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((impersonationId) => {
this.hasImpersonationId = !!impersonationId;
});
this.route.queryParams this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => { .subscribe((params) => {
@ -77,14 +87,18 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
this.hasPermissionToCreateWatchlistItem = hasPermission( this.hasPermissionToCreateWatchlistItem =
this.user.permissions, !this.hasImpersonationId &&
permissions.createWatchlistItem hasPermission(
); this.user.permissions,
this.hasPermissionToDeleteWatchlistItem = hasPermission( permissions.createWatchlistItem
this.user.permissions, );
permissions.deleteWatchlistItem this.hasPermissionToDeleteWatchlistItem =
); !this.hasImpersonationId &&
hasPermission(
this.user.permissions,
permissions.deleteWatchlistItem
);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }

2
apps/client/src/app/components/home-watchlist/home-watchlist.html

@ -20,7 +20,7 @@
</div> </div>
</div> </div>
</div> </div>
@if (hasPermissionToCreateWatchlistItem) { @if (!hasImpersonationId && hasPermissionToCreateWatchlistItem) {
<div class="fab-container"> <div class="fab-container">
<a <a
class="align-items-center d-flex justify-content-center" class="align-items-center d-flex justify-content-center"

Loading…
Cancel
Save