Browse Source

Merge branch 'main' into bugfix/fix-rounding-of-y-axis-ticks-in-benchmark-comparator

pull/1521/head
Thomas Kaul 3 years ago
committed by GitHub
parent
commit
10324306f8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      CHANGELOG.md
  2. 4
      apps/client/src/app/components/position/position.component.html
  3. 5
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  4. 66
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  5. 79
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  6. 5
      apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts
  7. 4
      package.json
  8. 22
      yarn.lock

8
CHANGELOG.md

@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added the position detail dialog to the _Top 3_ and _Bottom 3_ performers of the analysis page
### Changed
- Upgraded `color` from version `4.0.1` to `4.2.3`
### Fixed ### Fixed
- Fixed the rounding of the y-axis ticks in the benchmark comparator - Fixed the rounding of the y-axis ticks in the benchmark comparator

4
apps/client/src/app/components/position/position.component.html

@ -3,12 +3,12 @@
<a <a
class="d-flex p-3 w-100" class="d-flex p-3 w-100"
[ngClass]="{ 'cursor-default': isLoading }" [ngClass]="{ 'cursor-default': isLoading }"
[routerLink]="[]"
[queryParams]="{ [queryParams]="{
dataSource: position?.dataSource, dataSource: position?.dataSource,
positionDetailDialog: true, positionDetailDialog: true,
symbol: position?.symbol symbol: position?.symbol
}" }"
[routerLink]="[]"
> >
<div class="d-flex mr-2"> <div class="d-flex mr-2">
<gf-trend-indicator <gf-trend-indicator
@ -39,7 +39,7 @@
<div *ngIf="!isLoading" class="flex-grow-1 text-truncate"> <div *ngIf="!isLoading" class="flex-grow-1 text-truncate">
<div class="h6 m-0 text-truncate">{{ position?.name }}</div> <div class="h6 m-0 text-truncate">{{ position?.name }}</div>
<div class="d-flex"> <div class="d-flex">
<span>{{ position?.symbol | gfSymbol }}</span> <small class="text-muted">{{ position?.symbol | gfSymbol }}</small>
</div> </div>
<div class="d-flex mt-1"> <div class="d-flex mt-1">
<gf-value <gf-value

5
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -22,7 +22,7 @@ import { Market, ToggleOption } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { Account, AssetClass, DataSource } from '@prisma/client'; import { Account, AssetClass, DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject } from 'rxjs';
import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
@Component({ @Component({
@ -71,7 +71,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
| 'value' | 'value'
>; >;
}; };
public routeQueryParams: Subscription;
public sectors: { public sectors: {
[name: string]: { name: string; value: number }; [name: string]: { name: string; value: number };
}; };
@ -98,7 +97,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
private router: Router, private router: Router,
private userService: UserService private userService: UserService
) { ) {
this.routeQueryParams = route.queryParams route.queryParams
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => { .subscribe((params) => {
if (params['accountId'] && params['accountDetailDialog']) { if (params['accountId'] && params['accountDetailDialog']) {

66
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -1,4 +1,8 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component'; import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
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 { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
@ -9,8 +13,9 @@ import {
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types'; import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types';
import { SymbolProfile } from '@prisma/client'; import { DataSource, SymbolProfile } from '@prisma/client';
import { differenceInDays } from 'date-fns'; import { differenceInDays } from 'date-fns';
import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
@ -54,12 +59,30 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
private dialog: MatDialog,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService, private impersonationStorageService: ImpersonationStorageService,
private route: ActivatedRoute,
private router: Router,
private userService: UserService private userService: UserService
) { ) {
const { benchmarks } = this.dataService.fetchInfo(); const { benchmarks } = this.dataService.fetchInfo();
this.benchmarks = benchmarks; this.benchmarks = benchmarks;
route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (
params['dataSource'] &&
params['positionDetailDialog'] &&
params['symbol']
) {
this.openPositionDialog({
dataSource: params['dataSource'],
symbol: params['symbol']
});
}
});
} }
public ngOnInit() { public ngOnInit() {
@ -128,6 +151,47 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private openPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((user) => {
this.user = user;
const dialogRef = this.dialog.open(PositionDetailDialog, {
autoFocus: false,
data: <PositionDetailDialogParams>{
dataSource,
symbol,
baseCurrency: this.user?.settings?.baseCurrency,
colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId,
hasPermissionToReportDataGlitch: hasPermission(
this.user?.permissions,
permissions.reportDataGlitch
),
locale: this.user?.settings?.locale
},
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route });
});
});
}
private update() { private update() {
this.isLoadingBenchmarkComparator = true; this.isLoadingBenchmarkComparator = true;
this.isLoadingInvestmentChart = true; this.isLoadingInvestmentChart = true;

79
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -35,20 +35,30 @@
> >
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<div *ngFor="let position of top3; let i = index" class="d-flex py-1"> <div *ngFor="let position of top3; let i = index" class="py-1">
<div class="flex-grow-1 mr-2 text-truncate"> <a
{{ i + 1 }}. {{ position.name }} class="d-flex"
</div> [queryParams]="{
<div class="d-flex justify-content-end"> dataSource: position.dataSource,
<gf-value positionDetailDialog: true,
class="justify-content-end" symbol: position.symbol
position="end" }"
[colorizeSign]="true" [routerLink]="[]"
[isPercent]="true" >
[locale]="user?.settings?.locale" <div class="flex-grow-1 mr-2 text-truncate">
[value]="position.netPerformancePercentage" {{ i + 1 }}. {{ position.name }}
></gf-value> </div>
</div> <div class="d-flex justify-content-end">
<gf-value
class="justify-content-end"
position="end"
[colorizeSign]="true"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="position.netPerformancePercentage"
></gf-value>
</div>
</a>
</div> </div>
<div> <div>
<ngx-skeleton-loader <ngx-skeleton-loader
@ -71,23 +81,30 @@
> >
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<div <div *ngFor="let position of bottom3; let i = index" class="py-1">
*ngFor="let position of bottom3; let i = index" <a
class="d-flex py-1" class="d-flex"
> [queryParams]="{
<div class="flex-grow-1 mr-2 text-truncate"> dataSource: position.dataSource,
{{ i + 1 }}. {{ position.name }} positionDetailDialog: true,
</div> symbol: position.symbol
<div class="d-flex justify-content-end"> }"
<gf-value [routerLink]="[]"
class="justify-content-end" >
position="end" <div class="flex-grow-1 mr-2 text-truncate">
[colorizeSign]="true" {{ i + 1 }}. {{ position.name }}
[isPercent]="true" </div>
[locale]="user?.settings?.locale" <div class="d-flex justify-content-end">
[value]="position.netPerformancePercentage" <gf-value
></gf-value> class="justify-content-end"
</div> position="end"
[colorizeSign]="true"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="position.netPerformancePercentage"
></gf-value>
</div>
</a>
</div> </div>
<div> <div>
<ngx-skeleton-loader <ngx-skeleton-loader

5
apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts

@ -16,7 +16,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { AssetClass, DataSource } from '@prisma/client'; import { AssetClass, DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject } from 'rxjs';
import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
@Component({ @Component({
@ -36,7 +36,6 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
public placeholder = ''; public placeholder = '';
public portfolioDetails: PortfolioDetails; public portfolioDetails: PortfolioDetails;
public positionsArray: PortfolioPosition[]; public positionsArray: PortfolioPosition[];
public routeQueryParams: Subscription;
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -51,7 +50,7 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
private router: Router, private router: Router,
private userService: UserService private userService: UserService
) { ) {
this.routeQueryParams = route.queryParams route.queryParams
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => { .subscribe((params) => {
if ( if (

4
package.json

@ -101,7 +101,7 @@
"cheerio": "1.0.0-rc.12", "cheerio": "1.0.0-rc.12",
"class-transformer": "0.3.2", "class-transformer": "0.3.2",
"class-validator": "0.13.1", "class-validator": "0.13.1",
"color": "4.0.1", "color": "4.2.3",
"countries-list": "2.6.1", "countries-list": "2.6.1",
"countup.js": "2.0.7", "countup.js": "2.0.7",
"date-fns": "2.29.3", "date-fns": "2.29.3",
@ -160,7 +160,7 @@
"@types/big.js": "6.1.6", "@types/big.js": "6.1.6",
"@types/bull": "3.15.9", "@types/bull": "3.15.9",
"@types/cache-manager": "3.4.2", "@types/cache-manager": "3.4.2",
"@types/color": "3.0.2", "@types/color": "3.0.3",
"@types/google-spreadsheet": "3.1.5", "@types/google-spreadsheet": "3.1.5",
"@types/jest": "28.1.8", "@types/jest": "28.1.8",
"@types/lodash": "4.14.174", "@types/lodash": "4.14.174",

22
yarn.lock

@ -4749,10 +4749,10 @@
resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz" resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/color@3.0.2": "@types/color@3.0.3":
version "3.0.2" version "3.0.3"
resolved "https://registry.npmjs.org/@types/color/-/color-3.0.2.tgz" resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.3.tgz#e6d8d72b7aaef4bb9fe80847c26c7c786191016d"
integrity sha512-INiJl6sfNn8iyC5paxVzqiVUEj2boIlFki02uRTAkKwAj++7aAF+ZfEv/XrIeBa0XI/fTZuDHW8rEEcEVnON+Q== integrity sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==
dependencies: dependencies:
"@types/color-convert" "*" "@types/color-convert" "*"
@ -7688,9 +7688,9 @@ color-name@^1.0.0, color-name@~1.1.4:
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.6.0: color-string@^1.9.0:
version "1.9.1" version "1.9.1"
resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
dependencies: dependencies:
color-name "^1.0.0" color-name "^1.0.0"
@ -7701,13 +7701,13 @@ color-support@^1.1.2, color-support@^1.1.3:
resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
color@4.0.1: color@4.2.3:
version "4.0.1" version "4.2.3"
resolved "https://registry.npmjs.org/color/-/color-4.0.1.tgz" resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
integrity sha512-rpZjOKN5O7naJxkH2Rx1sZzzBgaiWECc6BYXjeCE6kF0kcASJYbUq02u7JqIHwCb/j3NhV+QhRL2683aICeGZA== integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
dependencies: dependencies:
color-convert "^2.0.1" color-convert "^2.0.1"
color-string "^1.6.0" color-string "^1.9.0"
colord@^2.9.1: colord@^2.9.1:
version "2.9.2" version "2.9.2"

Loading…
Cancel
Save