Feature/add data provider errors to api response (#738)
* Add data provider error details * Update changelog
This commit is contained in:
parent
3de7d3f60e
commit
86acbf06f4
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Included data provider errors in API response
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improved the account calculations
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||
import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||
import Big from 'big.js';
|
||||
|
||||
export interface CurrentPositions {
|
||||
hasErrors: boolean;
|
||||
export interface CurrentPositions extends ResponseError {
|
||||
positions: TimelinePosition[];
|
||||
grossPerformance: Big;
|
||||
grossPerformancePercentage: Big;
|
||||
|
@ -66,6 +66,7 @@ describe('PortfolioCalculatorNew', () => {
|
||||
|
||||
expect(currentPositions).toEqual({
|
||||
currentValue: new Big('0'),
|
||||
errors: [],
|
||||
grossPerformance: new Big('-12.6'),
|
||||
grossPerformancePercentage: new Big('-0.0440867739678096571'),
|
||||
hasErrors: false,
|
||||
|
@ -55,6 +55,7 @@ describe('PortfolioCalculatorNew', () => {
|
||||
|
||||
expect(currentPositions).toEqual({
|
||||
currentValue: new Big('297.8'),
|
||||
errors: [],
|
||||
grossPerformance: new Big('24.6'),
|
||||
grossPerformancePercentage: new Big('0.09004392386530014641'),
|
||||
hasErrors: false,
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { TimelineInfoInterface } from '@ghostfolio/api/app/portfolio/interfaces/timeline-info.interface';
|
||||
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
ResponseError,
|
||||
TimelinePosition,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Type as TypeOfOrder } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
@ -232,6 +236,8 @@ export class PortfolioCalculatorNew {
|
||||
const positions: TimelinePosition[] = [];
|
||||
let hasAnySymbolMetricsErrors = false;
|
||||
|
||||
const errors: ResponseError['errors'] = [];
|
||||
|
||||
for (const item of lastTransactionPoint.items) {
|
||||
const marketValue = marketSymbolMap[todayString]?.[item.symbol];
|
||||
|
||||
@ -272,12 +278,17 @@ export class PortfolioCalculatorNew {
|
||||
symbol: item.symbol,
|
||||
transactionCount: item.transactionCount
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
errors.push({ dataSource: item.dataSource, symbol: item.symbol });
|
||||
}
|
||||
}
|
||||
|
||||
const overall = this.calculateOverallPerformance(positions, initialValues);
|
||||
|
||||
return {
|
||||
...overall,
|
||||
errors,
|
||||
positions,
|
||||
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
PortfolioChart,
|
||||
PortfolioDetails,
|
||||
PortfolioInvestments,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioPublicDetails,
|
||||
PortfolioReport,
|
||||
PortfolioSummary
|
||||
@ -204,10 +204,11 @@ export class PortfolioController {
|
||||
|
||||
@Get('performance')
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||
public async getPerformance(
|
||||
@Headers('impersonation-id') impersonationId: string,
|
||||
@Query('range') range
|
||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||
): Promise<PortfolioPerformanceResponse> {
|
||||
const performanceInformation = await this.portfolioServiceStrategy
|
||||
.get()
|
||||
.getPerformance(impersonationId, range);
|
||||
|
@ -24,7 +24,7 @@ import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
Accounts,
|
||||
PortfolioDetails,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
@ -730,7 +730,7 @@ export class PortfolioServiceNew {
|
||||
public async getPerformance(
|
||||
aImpersonationId: string,
|
||||
aDateRange: DateRange = 'max'
|
||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||
): Promise<PortfolioPerformanceResponse> {
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
@ -776,6 +776,7 @@ export class PortfolioServiceNew {
|
||||
currentPositions.netPerformancePercentage.toNumber();
|
||||
|
||||
return {
|
||||
errors: currentPositions.errors,
|
||||
hasErrors: currentPositions.hasErrors || hasErrors,
|
||||
performance: {
|
||||
currentGrossPerformance,
|
||||
|
@ -25,7 +25,7 @@ import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
Accounts,
|
||||
PortfolioDetails,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
@ -712,7 +712,7 @@ export class PortfolioService {
|
||||
public async getPerformance(
|
||||
aImpersonationId: string,
|
||||
aDateRange: DateRange = 'max'
|
||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||
): Promise<PortfolioPerformanceResponse> {
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator(
|
||||
|
@ -41,6 +41,14 @@ export class TransformDataSourceInResponseInterceptor<T>
|
||||
data.dataSource = encodeDataSource(data.dataSource);
|
||||
}
|
||||
|
||||
if (data.errors) {
|
||||
for (const error of data.errors) {
|
||||
if (error.dataSource) {
|
||||
error.dataSource = encodeDataSource(error.dataSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.holdings) {
|
||||
for (const symbol of Object.keys(data.holdings)) {
|
||||
if (data.holdings[symbol].dataSource) {
|
||||
|
@ -7,7 +7,11 @@ import {
|
||||
} from '@ghostfolio/client/services/settings-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { defaultDateRangeOptions } from '@ghostfolio/common/config';
|
||||
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
PortfolioPerformance,
|
||||
UniqueAsset,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { DateRange } from '@ghostfolio/common/types';
|
||||
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
|
||||
@ -24,6 +28,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||
public dateRange: DateRange;
|
||||
public dateRangeOptions = defaultDateRangeOptions;
|
||||
public deviceType: string;
|
||||
public errors: UniqueAsset[];
|
||||
public hasError: boolean;
|
||||
public hasImpersonationId: boolean;
|
||||
public hasPermissionToCreateOrder: boolean;
|
||||
@ -126,6 +131,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||
.fetchPortfolioPerformance({ range: this.dateRange })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((response) => {
|
||||
this.errors = response.errors;
|
||||
this.hasError = response.hasErrors;
|
||||
this.performance = response.performance;
|
||||
this.isLoadingPerformance = false;
|
||||
|
@ -28,6 +28,7 @@
|
||||
class="pb-4"
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[deviceType]="deviceType"
|
||||
[errors]="errors"
|
||||
[hasError]="hasError"
|
||||
[isAllTimeHigh]="isAllTimeHigh"
|
||||
[isAllTimeLow]="isAllTimeLow"
|
||||
|
@ -7,6 +7,7 @@
|
||||
? 'Sorry! Our data provider partner is experiencing the hiccups.'
|
||||
: ''
|
||||
"
|
||||
(click)="errors?.length > 0 && onShowErrors()"
|
||||
>
|
||||
<ion-icon
|
||||
*ngIf="hasError && !isLoading"
|
||||
|
@ -7,7 +7,10 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { PortfolioPerformance } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
PortfolioPerformance,
|
||||
ResponseError
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { CountUp } from 'countup.js';
|
||||
import { isNumber } from 'lodash';
|
||||
|
||||
@ -20,6 +23,7 @@ import { isNumber } from 'lodash';
|
||||
export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
||||
@Input() baseCurrency: string;
|
||||
@Input() deviceType: string;
|
||||
@Input() errors: ResponseError['errors'];
|
||||
@Input() hasError: boolean;
|
||||
@Input() isAllTimeHigh: boolean;
|
||||
@Input() isAllTimeLow: boolean;
|
||||
@ -69,4 +73,12 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onShowErrors() {
|
||||
const errorMessageParts = this.errors.map((error) => {
|
||||
return `${error.symbol} (${error.dataSource})`;
|
||||
});
|
||||
|
||||
alert(errorMessageParts.join('\n'));
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,11 @@ import {
|
||||
PortfolioDetails,
|
||||
PortfolioInvestments,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioPublicDetails,
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
UniqueAsset,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { permissions } from '@ghostfolio/common/permissions';
|
||||
@ -188,13 +190,13 @@ export class DataService {
|
||||
});
|
||||
}
|
||||
|
||||
public fetchPortfolioPerformance(aParams: { [param: string]: any }) {
|
||||
return this.http.get<{
|
||||
hasErrors: boolean;
|
||||
performance: PortfolioPerformance;
|
||||
}>('/api/portfolio/performance', {
|
||||
params: aParams
|
||||
});
|
||||
public fetchPortfolioPerformance(params: { [param: string]: any }) {
|
||||
return this.http.get<PortfolioPerformanceResponse>(
|
||||
'/api/portfolio/performance',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public fetchPortfolioPublic(aId: string) {
|
||||
|
@ -21,6 +21,8 @@ import { PortfolioReportRule } from './portfolio-report-rule.interface';
|
||||
import { PortfolioReport } from './portfolio-report.interface';
|
||||
import { PortfolioSummary } from './portfolio-summary.interface';
|
||||
import { Position } from './position.interface';
|
||||
import { ResponseError } from './responses/errors.interface';
|
||||
import { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
|
||||
import { TimelinePosition } from './timeline-position.interface';
|
||||
import { UniqueAsset } from './unique-asset.interface';
|
||||
import { UserSettings } from './user-settings.interface';
|
||||
@ -43,12 +45,14 @@ export {
|
||||
PortfolioItem,
|
||||
PortfolioOverview,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioPosition,
|
||||
PortfolioPublicDetails,
|
||||
PortfolioReport,
|
||||
PortfolioReportRule,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
ResponseError,
|
||||
TimelinePosition,
|
||||
UniqueAsset,
|
||||
User,
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { UniqueAsset } from '../unique-asset.interface';
|
||||
|
||||
export interface ResponseError {
|
||||
errors?: UniqueAsset[];
|
||||
hasErrors: boolean;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { PortfolioPerformance } from '../portfolio-performance.interface';
|
||||
import { ResponseError } from './errors.interface';
|
||||
|
||||
export interface PortfolioPerformanceResponse extends ResponseError {
|
||||
performance: PortfolioPerformance;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user