Feature/more discreet data provider warning (#589)
* Upgrade http-status-codes to version 2.2.0 * Make the data provider warning more discreet * Update changelog
This commit is contained in:
parent
994275e093
commit
db1d474ddf
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Made the data provider warning more discreet
|
||||||
|
- Upgraded `http-status-codes` from version `2.1.4` to `2.2.0`
|
||||||
- Upgraded `ngx-device-detector` from version `2.1.1` to `3.0.0`
|
- Upgraded `ngx-device-detector` from version `2.1.1` to `3.0.0`
|
||||||
- Upgraded `ngx-markdown` from version `12.0.1` to `13.0.0`
|
- Upgraded `ngx-markdown` from version `12.0.1` to `13.0.0`
|
||||||
- Upgraded `ngx-stripe` from version `12.0.2` to `13.0.0`
|
- Upgraded `ngx-stripe` from version `12.0.2` to `13.0.0`
|
||||||
|
@ -51,7 +51,7 @@ export class PortfolioController {
|
|||||||
@Get('investments')
|
@Get('investments')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async findAll(
|
public async findAll(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<InvestmentItem[]> {
|
): Promise<InvestmentItem[]> {
|
||||||
if (
|
if (
|
||||||
@ -87,7 +87,7 @@ export class PortfolioController {
|
|||||||
@Get('chart')
|
@Get('chart')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getChart(
|
public async getChart(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioChart> {
|
): Promise<PortfolioChart> {
|
||||||
@ -98,18 +98,14 @@ export class PortfolioController {
|
|||||||
|
|
||||||
let chartData = historicalDataContainer.items;
|
let chartData = historicalDataContainer.items;
|
||||||
|
|
||||||
let hasNullValue = false;
|
let hasError = false;
|
||||||
|
|
||||||
chartData.forEach((chartDataItem) => {
|
chartData.forEach((chartDataItem) => {
|
||||||
if (hasNotDefinedValuesInObject(chartDataItem)) {
|
if (hasNotDefinedValuesInObject(chartDataItem)) {
|
||||||
hasNullValue = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasNullValue) {
|
|
||||||
res.status(StatusCodes.ACCEPTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
this.userService.isRestrictedView(this.request.user)
|
this.userService.isRestrictedView(this.request.user)
|
||||||
@ -131,6 +127,7 @@ export class PortfolioController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <any>res.json({
|
return <any>res.json({
|
||||||
|
hasError,
|
||||||
chart: chartData,
|
chart: chartData,
|
||||||
isAllTimeHigh: historicalDataContainer.isAllTimeHigh,
|
isAllTimeHigh: historicalDataContainer.isAllTimeHigh,
|
||||||
isAllTimeLow: historicalDataContainer.isAllTimeLow
|
isAllTimeLow: historicalDataContainer.isAllTimeLow
|
||||||
@ -140,7 +137,7 @@ export class PortfolioController {
|
|||||||
@Get('details')
|
@Get('details')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getDetails(
|
public async getDetails(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioDetails> {
|
): Promise<PortfolioDetails> {
|
||||||
@ -152,6 +149,8 @@ export class PortfolioController {
|
|||||||
return <any>res.json({ accounts: {}, holdings: {} });
|
return <any>res.json({ accounts: {}, holdings: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasError = false;
|
||||||
|
|
||||||
const { accounts, holdings, hasErrors } =
|
const { accounts, holdings, hasErrors } =
|
||||||
await this.portfolioService.getDetails(
|
await this.portfolioService.getDetails(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
@ -160,7 +159,7 @@ export class PortfolioController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
||||||
res.status(StatusCodes.ACCEPTED);
|
hasError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -198,43 +197,38 @@ export class PortfolioController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <any>res.json({ accounts, holdings });
|
return <any>res.json({ accounts, hasError, holdings });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('performance')
|
@Get('performance')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getPerformance(
|
public async getPerformance(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioPerformance> {
|
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||||
const performanceInformation = await this.portfolioService.getPerformance(
|
const performanceInformation = await this.portfolioService.getPerformance(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
range
|
range
|
||||||
);
|
);
|
||||||
|
|
||||||
if (performanceInformation?.hasErrors) {
|
|
||||||
res.status(StatusCodes.ACCEPTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let performance = performanceInformation.performance;
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
this.userService.isRestrictedView(this.request.user)
|
this.userService.isRestrictedView(this.request.user)
|
||||||
) {
|
) {
|
||||||
performance = nullifyValuesInObject(performance, [
|
performanceInformation.performance = nullifyValuesInObject(
|
||||||
'currentGrossPerformance',
|
performanceInformation.performance,
|
||||||
'currentValue'
|
['currentGrossPerformance', 'currentValue']
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <any>res.json(performance);
|
return <any>res.json(performanceInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('positions')
|
@Get('positions')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getPositions(
|
public async getPositions(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioPositions> {
|
): Promise<PortfolioPositions> {
|
||||||
@ -243,10 +237,6 @@ export class PortfolioController {
|
|||||||
range
|
range
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result?.hasErrors) {
|
|
||||||
res.status(StatusCodes.ACCEPTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
this.userService.isRestrictedView(this.request.user)
|
this.userService.isRestrictedView(this.request.user)
|
||||||
@ -353,7 +343,7 @@ export class PortfolioController {
|
|||||||
@Get('position/:symbol')
|
@Get('position/:symbol')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getPosition(
|
public async getPosition(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Param('symbol') symbol
|
@Param('symbol') symbol
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
let position = await this.portfolioService.getPosition(
|
let position = await this.portfolioService.getPosition(
|
||||||
@ -387,7 +377,7 @@ export class PortfolioController {
|
|||||||
@Get('report')
|
@Get('report')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getReport(
|
public async getReport(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioReport> {
|
): Promise<PortfolioReport> {
|
||||||
if (
|
if (
|
||||||
|
@ -29,6 +29,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
{ label: 'Max', value: 'max' }
|
{ label: 'Max', value: 'max' }
|
||||||
];
|
];
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
|
public hasError: boolean;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public historicalDataItems: LineChartItem[];
|
public historicalDataItems: LineChartItem[];
|
||||||
public isAllTimeHigh: boolean;
|
public isAllTimeHigh: boolean;
|
||||||
@ -116,7 +117,8 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
.fetchPortfolioPerformance({ range: this.dateRange })
|
.fetchPortfolioPerformance({ range: this.dateRange })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((response) => {
|
.subscribe((response) => {
|
||||||
this.performance = response;
|
this.hasError = response.hasErrors;
|
||||||
|
this.performance = response.performance;
|
||||||
this.isLoadingPerformance = false;
|
this.isLoadingPerformance = false;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
|
@ -1,15 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
class="
|
class="align-items-center container d-flex flex-column h-100 justify-content-center overview p-0 position-relative"
|
||||||
align-items-center
|
|
||||||
container
|
|
||||||
d-flex
|
|
||||||
flex-column
|
|
||||||
h-100
|
|
||||||
justify-content-center
|
|
||||||
overview
|
|
||||||
p-0
|
|
||||||
position-relative
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<div class="row w-100">
|
<div class="row w-100">
|
||||||
<div class="chart-container col">
|
<div class="chart-container col">
|
||||||
@ -37,6 +27,8 @@
|
|||||||
<gf-portfolio-performance
|
<gf-portfolio-performance
|
||||||
class="pb-4"
|
class="pb-4"
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[deviceType]="deviceType"
|
||||||
|
[hasError]="hasError"
|
||||||
[isAllTimeHigh]="isAllTimeHigh"
|
[isAllTimeHigh]="isAllTimeHigh"
|
||||||
[isAllTimeLow]="isAllTimeLow"
|
[isAllTimeLow]="isAllTimeLow"
|
||||||
[isLoading]="isLoadingPerformance"
|
[isLoading]="isLoadingPerformance"
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
<div
|
<div class="no-gutters row">
|
||||||
class="no-gutters row"
|
<div
|
||||||
[ngClass]="{
|
class="flex-grow-1 status text-muted text-right"
|
||||||
'text-danger': isAllTimeLow,
|
[title]="
|
||||||
'text-success': isAllTimeHigh
|
hasError
|
||||||
}"
|
? 'Sorry! Our data provider partner is experiencing the hiccups.'
|
||||||
>
|
: ''
|
||||||
<div class="flex-grow-1"></div>
|
"
|
||||||
|
>
|
||||||
|
<ion-icon *ngIf="hasError" name="alert-circle-outline"></ion-icon>
|
||||||
|
</div>
|
||||||
<div *ngIf="isLoading" class="align-items-center d-flex">
|
<div *ngIf="isLoading" class="align-items-center d-flex">
|
||||||
<ngx-skeleton-loader
|
<ngx-skeleton-loader
|
||||||
animation="pulse"
|
animation="pulse"
|
||||||
@ -20,6 +23,10 @@
|
|||||||
<div
|
<div
|
||||||
class="display-4 font-weight-bold m-0 text-center value-container"
|
class="display-4 font-weight-bold m-0 text-center value-container"
|
||||||
[hidden]="isLoading"
|
[hidden]="isLoading"
|
||||||
|
[ngClass]="{
|
||||||
|
'text-danger': isAllTimeLow,
|
||||||
|
'text-success': isAllTimeHigh
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<span #value id="value"></span>
|
<span #value id="value"></span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-size: 1.33rem;
|
||||||
|
}
|
||||||
|
|
||||||
.value-container {
|
.value-container {
|
||||||
#value {
|
#value {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
@ -19,6 +19,8 @@ import { isNumber } from 'lodash';
|
|||||||
})
|
})
|
||||||
export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
|
@Input() deviceType: string;
|
||||||
|
@Input() hasError: boolean;
|
||||||
@Input() isAllTimeHigh: boolean;
|
@Input() isAllTimeHigh: boolean;
|
||||||
@Input() isAllTimeLow: boolean;
|
@Input() isAllTimeLow: boolean;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@ -44,7 +46,11 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
|||||||
this.unit = this.baseCurrency;
|
this.unit = this.baseCurrency;
|
||||||
|
|
||||||
new CountUp('value', this.performance?.currentValue, {
|
new CountUp('value', this.performance?.currentValue, {
|
||||||
decimalPlaces: 2,
|
decimalPlaces:
|
||||||
|
this.deviceType === 'mobile' &&
|
||||||
|
this.performance?.currentValue >= 100000
|
||||||
|
? 0
|
||||||
|
: 2,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
separator: `'`
|
separator: `'`
|
||||||
}).start();
|
}).start();
|
||||||
|
@ -4,8 +4,7 @@ import {
|
|||||||
HttpEvent,
|
HttpEvent,
|
||||||
HttpHandler,
|
HttpHandler,
|
||||||
HttpInterceptor,
|
HttpInterceptor,
|
||||||
HttpRequest,
|
HttpRequest
|
||||||
HttpResponse
|
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
@ -43,26 +42,6 @@ export class HttpResponseInterceptor implements HttpInterceptor {
|
|||||||
): Observable<HttpEvent<any>> {
|
): Observable<HttpEvent<any>> {
|
||||||
return next.handle(request).pipe(
|
return next.handle(request).pipe(
|
||||||
tap((event: HttpEvent<any>) => {
|
tap((event: HttpEvent<any>) => {
|
||||||
if (event instanceof HttpResponse) {
|
|
||||||
if (event.status === StatusCodes.ACCEPTED) {
|
|
||||||
if (!this.snackBarRef) {
|
|
||||||
this.snackBarRef = this.snackBar.open(
|
|
||||||
'Sorry! Our data provider partner is experiencing a mild case of the hiccups ;(',
|
|
||||||
'Try again?',
|
|
||||||
{ duration: 6000 }
|
|
||||||
);
|
|
||||||
|
|
||||||
this.snackBarRef.afterDismissed().subscribe(() => {
|
|
||||||
this.snackBarRef = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.snackBarRef.onAction().subscribe(() => {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}),
|
}),
|
||||||
catchError((error: HttpErrorResponse) => {
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
@ -181,7 +181,10 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioPerformance(aParams: { [param: string]: any }) {
|
public fetchPortfolioPerformance(aParams: { [param: string]: any }) {
|
||||||
return this.http.get<PortfolioPerformance>('/api/portfolio/performance', {
|
return this.http.get<{
|
||||||
|
hasErrors: boolean;
|
||||||
|
performance: PortfolioPerformance;
|
||||||
|
}>('/api/portfolio/performance', {
|
||||||
params: aParams
|
params: aParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { HistoricalDataItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position-detail.interface';
|
import { HistoricalDataItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position-detail.interface';
|
||||||
|
|
||||||
export interface PortfolioChart {
|
export interface PortfolioChart {
|
||||||
|
hasError: boolean;
|
||||||
isAllTimeHigh: boolean;
|
isAllTimeHigh: boolean;
|
||||||
isAllTimeLow: boolean;
|
isAllTimeLow: boolean;
|
||||||
chart: HistoricalDataItem[];
|
chart: HistoricalDataItem[];
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
"cryptocurrencies": "7.0.0",
|
"cryptocurrencies": "7.0.0",
|
||||||
"date-fns": "2.22.1",
|
"date-fns": "2.22.1",
|
||||||
"envalid": "7.2.1",
|
"envalid": "7.2.1",
|
||||||
"http-status-codes": "2.1.4",
|
"http-status-codes": "2.2.0",
|
||||||
"ionicons": "5.5.1",
|
"ionicons": "5.5.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"ngx-device-detector": "3.0.0",
|
"ngx-device-detector": "3.0.0",
|
||||||
|
@ -10032,10 +10032,10 @@ http-signature@~1.2.0:
|
|||||||
jsprim "^1.2.2"
|
jsprim "^1.2.2"
|
||||||
sshpk "^1.7.0"
|
sshpk "^1.7.0"
|
||||||
|
|
||||||
http-status-codes@2.1.4:
|
http-status-codes@2.2.0:
|
||||||
version "2.1.4"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.1.4.tgz#453d99b4bd9424254c4f6a9a3a03715923052798"
|
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be"
|
||||||
integrity sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==
|
integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==
|
||||||
|
|
||||||
https-browserify@^1.0.0:
|
https-browserify@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user