Feature/improve watchlist for impersonation mode (#4632)
* Improve watchlist for impersonation mode * Update changelog
This commit is contained in:
@ -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
|
||||||
|
|
||||||
|
@ -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,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,
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
Reference in New Issue
Block a user