Feature/improve impersonation mode (#293)

* Improve the impersonation mode

* Update changelog
pull/294/head
Thomas Kaul 3 years ago committed by GitHub
parent 308b218487
commit 0ee2258af8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Fixed
- Fixed an issue with the performance in the portfolio summary tab on the home page (impersonation mode)
- Fixed various values in the impersonation mode which have not been nullified
### Removed
- Removed the current net performance
- Removed the read foreign portfolio permission
## 1.38.0 - 14.08.2021
### Added

@ -84,25 +84,19 @@ export class AccountController {
public async getAllAccounts(
@Headers('impersonation-id') impersonationId
): Promise<AccountModel[]> {
const impersonationUserId = await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
let accounts = await this.accountService.accounts({
include: { Order: true, Platform: true },
orderBy: { name: 'asc' },
where: { userId: impersonationUserId || this.request.user.id }
});
let accounts = await this.accountService.getAccounts(
impersonationUserId || this.request.user.id
);
if (
impersonationUserId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationUserId) {
accounts = nullifyValuesInObjects(accounts, [
'balance',
'fee',
'quantity',
'unitPrice'

@ -1,7 +1,7 @@
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Injectable } from '@nestjs/common';
import { Account, Currency, Order, Prisma } from '@prisma/client';
import { Account, Currency, Order, Platform, Prisma } from '@prisma/client';
import { CashDetails } from './interfaces/cash-details.interface';
@ -41,7 +41,12 @@ export class AccountService {
cursor?: Prisma.AccountWhereUniqueInput;
where?: Prisma.AccountWhereInput;
orderBy?: Prisma.AccountOrderByInput;
}): Promise<Account[]> {
}): Promise<
(Account & {
Order?: Order[];
Platform?: Platform;
})[]
> {
const { include, skip, take, cursor, where, orderBy } = params;
return this.prismaService.account.findMany({
@ -72,6 +77,22 @@ export class AccountService {
});
}
public async getAccounts(aUserId: string) {
const accounts = await this.accounts({
include: { Order: true, Platform: true },
orderBy: { name: 'asc' },
where: { userId: aUserId }
});
return accounts.map((account) => {
const result = { ...account, transactionCount: account.Order.length };
delete result.Order;
return result;
});
}
public async getCashDetails(
aUserId: string,
aCurrency: Currency

@ -88,13 +88,7 @@ export class OrderController {
where: { userId: impersonationUserId || this.request.user.id }
});
if (
impersonationUserId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationUserId) {
orders = nullifyValuesInObjects(orders, ['fee', 'quantity', 'unitPrice']);
}

@ -11,11 +11,6 @@ import {
PortfolioSummary
} from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import {
getPermissions,
hasPermission,
permissions
} from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
@ -30,7 +25,6 @@ import {
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import Big from 'big.js';
import { Response } from 'express';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@ -50,7 +44,7 @@ export class PortfolioController {
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get('/investments')
@Get('investments')
@UseGuards(AuthGuard('jwt'))
public async findAll(
@Headers('impersonation-id') impersonationId
@ -59,13 +53,7 @@ export class PortfolioController {
impersonationId
);
if (
impersonationId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationId) {
const maxInvestment = investments.reduce(
(investment, item) => Math.max(investment, item.investment),
1
@ -104,13 +92,7 @@ export class PortfolioController {
res.status(StatusCodes.ACCEPTED);
}
if (
impersonationId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationId) {
let maxValue = 0;
chartData.forEach((portfolioItem) => {
@ -139,17 +121,8 @@ export class PortfolioController {
): Promise<{ [symbol: string]: PortfolioPosition }> {
let details: { [symbol: string]: PortfolioPosition } = {};
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
try {
details = await this.portfolioService.getDetails(
impersonationUserId,
range
);
details = await this.portfolioService.getDetails(impersonationId, range);
} catch (error) {
console.error(error);
@ -160,13 +133,7 @@ export class PortfolioController {
res.status(StatusCodes.ACCEPTED);
}
if (
impersonationId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationId) {
const totalInvestment = Object.values(details)
.map((portfolioPosition) => {
return portfolioPosition.investment;
@ -220,16 +187,9 @@ export class PortfolioController {
}
let performance = performanceInformation.performance;
if (
impersonationId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationId) {
performance = nullifyValuesInObject(performance, [
'currentGrossPerformance',
'currentNetPerformance',
'currentValue'
]);
}
@ -253,6 +213,16 @@ export class PortfolioController {
res.status(StatusCodes.ACCEPTED);
}
if (impersonationId) {
result.positions = result.positions.map((position) => {
return nullifyValuesInObject(position, [
'grossPerformance',
'investment',
'quantity'
]);
});
}
return <any>res.json(result);
}
@ -263,20 +233,14 @@ export class PortfolioController {
): Promise<PortfolioSummary> {
let summary = await this.portfolioService.getSummary(impersonationId);
if (
impersonationId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
if (impersonationId) {
summary = nullifyValuesInObject(summary, [
'cash',
'committedFunds',
'currentGrossPerformance',
'currentNetPerformance',
'currentValue',
'fees',
'netWorth',
'totalBuy',
'totalSell'
]);
@ -297,14 +261,12 @@ export class PortfolioController {
);
if (position) {
if (
impersonationId &&
!hasPermission(
getPermissions(this.request.user.role),
permissions.readForeignPortfolio
)
) {
position = nullifyValuesInObject(position, ['grossPerformance']);
if (impersonationId) {
position = nullifyValuesInObject(position, [
'grossPerformance',
'investment',
'quantity'
]);
}
return position;

@ -530,8 +530,6 @@ export class PortfolioService {
performance: {
currentGrossPerformance: 0,
currentGrossPerformancePercent: 0,
currentNetPerformance: 0,
currentNetPerformancePercent: 0,
currentValue: 0
}
};
@ -556,9 +554,6 @@ export class PortfolioService {
performance: {
currentGrossPerformance,
currentGrossPerformancePercent,
// TODO: the next two should include fees
currentNetPerformance: currentGrossPerformance,
currentNetPerformancePercent: currentGrossPerformancePercent,
currentValue: currentValue
}
};
@ -668,7 +663,7 @@ export class PortfolioService {
const currency = this.request.user.Settings.currency;
const userId = await this.getUserId(aImpersonationId);
const performanceInformation = await this.getPerformance(userId);
const performanceInformation = await this.getPerformance(aImpersonationId);
const { balance } = await this.accountService.getCashDetails(
userId,

@ -8,7 +8,7 @@
[tooltip]="element.Platform?.name"
[url]="element.Platform?.url"
></gf-symbol-icon>
<span>{{ element.name }}</span>
<span>{{ element.name }} </span>
<span
*ngIf="element.isDefault"
class="d-lg-inline-block d-none text-muted"
@ -45,7 +45,7 @@
<span class="d-none d-sm-block" i18n>Transactions</span>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.Order?.length }}
{{ element.transactionCount }}
</td>
</ng-container>

@ -37,7 +37,7 @@
[colorizeSign]="true"
[isCurrency]="true"
[locale]="locale"
[value]="isLoading ? undefined : performance?.currentNetPerformance"
[value]="isLoading ? undefined : performance?.currentGrossPerformance"
></gf-value>
</div>
<div class="col">
@ -46,7 +46,7 @@
[isPercent]="true"
[locale]="locale"
[value]="
isLoading ? undefined : performance?.currentNetPerformancePercent
isLoading ? undefined : performance?.currentGrossPerformancePercent
"
></gf-value>
</div>

@ -52,7 +52,7 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
new CountUp(
'value',
this.performance?.currentNetPerformancePercent * 100,
this.performance?.currentGrossPerformancePercent * 100,
{
decimalPlaces: 2,
duration: 0.75,

@ -1,16 +1,21 @@
<ng-container *ngIf="value || value === 0">
<ng-container *ngIf="value || value === 0 || value === null">
<div
class="d-flex"
[ngClass]="position === 'end' ? 'justify-content-end' : ''"
>
<ng-container *ngIf="isNumber">
<ng-container *ngIf="isNumber || value === null">
<div *ngIf="colorizeSign && value > 0" class="mr-1 text-success">+</div>
<div *ngIf="colorizeSign && value < 0" class="mr-1 text-danger">-</div>
<div *ngIf="isPercent" [ngClass]="size === 'medium' ? 'h4 mb-0' : ''">
{{ formattedValue }}%
</div>
<div *ngIf="!isPercent" [ngClass]="size === 'medium' ? 'h4 mb-0' : ''">
{{ formattedValue }}
<ng-container *ngIf="value === null">
<span class="text-monospace text-muted">***</span>
</ng-container>
<ng-container *ngIf="value !== null">
{{ formattedValue }}
</ng-container>
</div>
<small *ngIf="currency && size === 'medium'" class="ml-1">
{{ currency }}
@ -38,8 +43,3 @@
width: '5rem'
}"
></ngx-skeleton-loader>
<div *ngIf="value === null">
<span class="text-monospace text-muted text-right">***</span>
<span *ngIf="currency" class="ml-1 text-muted">{{ currency }}</span>
</div>

@ -58,7 +58,6 @@ export class HomePageComponent implements OnDestroy, OnInit {
public fearAndGreedIndex: number;
public hasImpersonationId: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public hasPermissionToReadForeignPortfolio: boolean;
public hasPositions: boolean;
public historicalDataItems: LineChartItem[];
public isLoadingPerformance = true;
@ -120,11 +119,6 @@ export class HomePageComponent implements OnDestroy, OnInit {
});
}
this.hasPermissionToReadForeignPortfolio = hasPermission(
this.user.permissions,
permissions.readForeignPortfolio
);
this.changeDetectorRef.markForCheck();
}
});

@ -61,7 +61,7 @@
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
[showDetails]="!hasImpersonationId"
></gf-portfolio-performance>
<div class="text-center">
<gf-toggle

@ -19,7 +19,6 @@ import {
Position,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
@ -37,7 +36,6 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
public dateRange: DateRange = 'max';
public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToReadForeignPortfolio: boolean;
public hasPositions: boolean;
public historicalDataItems: LineChartItem[];
public isLoadingPerformance = true;
@ -65,11 +63,6 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
if (state?.user) {
this.user = state.user;
this.hasPermissionToReadForeignPortfolio = hasPermission(
this.user.permissions,
permissions.readForeignPortfolio
);
this.changeDetectorRef.markForCheck();
}
});

@ -49,7 +49,7 @@
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
[showDetails]="!hasImpersonationId"
></gf-portfolio-performance>
</div>
</div>

@ -1,7 +1,5 @@
export interface PortfolioPerformance {
currentGrossPerformance: number;
currentGrossPerformancePercent: number;
currentNetPerformance: number;
currentNetPerformancePercent: number;
currentValue: number;
}

@ -19,7 +19,6 @@ export const permissions = {
enableSocialLogin: 'enableSocialLogin',
enableStatistics: 'enableStatistics',
enableSubscription: 'enableSubscription',
readForeignPortfolio: 'readForeignPortfolio',
updateAccount: 'updateAccount',
updateAuthDevice: 'updateAuthDevice',
updateOrder: 'updateOrder',
@ -45,7 +44,6 @@ export function getPermissions(aRole: Role): string[] {
permissions.deleteAuthDevice,
permissions.deleteOrder,
permissions.deleteUser,
permissions.readForeignPortfolio,
permissions.updateAccount,
permissions.updateAuthDevice,
permissions.updateOrder,

Loading…
Cancel
Save