diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9fdb10c..271828a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Filtered out positions without any quantity in the positions table +- Improved the symbol lookup: allow saving with valid symbol in create or edit transaction dialog ## 1.43.0 - 24.08.2021 diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index d388d0829..61dcba19a 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -11,6 +11,7 @@ import { import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; +import { isEmpty } from 'lodash'; import { LookupItem } from './interfaces/lookup-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface'; @@ -48,6 +49,15 @@ export class SymbolController { @Get(':symbol') @UseGuards(AuthGuard('jwt')) public async getPosition(@Param('symbol') symbol): Promise { - return this.symbolService.get(symbol); + const result = await this.symbolService.get(symbol); + + if (!result || isEmpty(result)) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + + return result; } } diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 7fcafc4cc..2be74b19b 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -15,13 +15,17 @@ export class SymbolService { public async get(aSymbol: string): Promise { const response = await this.dataProviderService.get([aSymbol]); - const { currency, dataSource, marketPrice } = response[aSymbol]; + const { currency, dataSource, marketPrice } = response[aSymbol] ?? {}; - return { - dataSource, - marketPrice, - currency: (currency) - }; + if (currency && dataSource && marketPrice) { + return { + dataSource, + marketPrice, + currency: (currency) + }; + } + + return undefined; } public async lookup(aQuery: string): Promise<{ items: LookupItem[] }> { diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts index 42930fb27..a8473ee64 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts @@ -11,8 +11,9 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { Currency } from '@prisma/client'; -import { Observable, Subject } from 'rxjs'; +import { EMPTY, Observable, Subject } from 'rxjs'; import { + catchError, debounceTime, distinctUntilChanged, startWith, @@ -49,7 +50,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateTransactionDialogParams ) {} - ngOnInit() { + public ngOnInit() { const { currencies, platforms } = this.dataService.fetchInfo(); this.currencies = currencies; @@ -84,17 +85,45 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { this.data.transaction.unitPrice = this.currentMarketPrice; } + public onBlurSymbol() { + const symbol = this.searchSymbolCtrl.value; + this.updateSymbol(symbol); + } + public onCancel(): void { this.dialogRef.close(); } public onUpdateSymbol(event: MatAutocompleteSelectedEvent) { + this.updateSymbol(event.option.value); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private updateSymbol(symbol: string) { this.isLoading = true; - this.data.transaction.symbol = event.option.value; + + this.data.transaction.symbol = symbol; this.dataService .fetchSymbolItem(this.data.transaction.symbol) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe( + catchError(() => { + this.data.transaction.currency = null; + this.data.transaction.dataSource = null; + this.data.transaction.unitPrice = null; + + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + + return EMPTY; + }), + takeUntil(this.unsubscribeSubject) + ) .subscribe(({ currency, dataSource, marketPrice }) => { this.data.transaction.currency = currency; this.data.transaction.dataSource = dataSource; @@ -105,17 +134,4 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { this.changeDetectorRef.markForCheck(); }); } - - public onUpdateSymbolByTyping(value: string) { - this.data.transaction.currency = null; - this.data.transaction.dataSource = null; - this.data.transaction.unitPrice = null; - - this.data.transaction.symbol = value; - } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html index a183b9ce3..f7a18abca 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html @@ -29,7 +29,7 @@ required [formControl]="searchSymbolCtrl" [matAutocomplete]="auto" - (change)="onUpdateSymbolByTyping($event.target.value)" + (blur)="onBlurSymbol()" />