diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b0cb190..d42cccdc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the import of historical market data in the admin control panel - Removed the account type from the `Account` database schema ## 2.19.0 - 2023-11-06 diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 5ccfda503..03dc717d5 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -8,6 +8,7 @@ import { } from '@angular/core'; import { FormBuilder, FormControl, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; @@ -25,8 +26,8 @@ import { } from '@prisma/client'; import { format } from 'date-fns'; import { parse as csvToJson } from 'papaparse'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { EMPTY, Subject } from 'rxjs'; +import { catchError, takeUntil } from 'rxjs/operators'; import { AssetProfileDialogParams } from './interfaces/interfaces'; @@ -50,6 +51,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit { assetClass: new FormControl(undefined), assetSubClass: new FormControl(undefined), comment: '', + historicalData: this.formBuilder.group({ + csvString: '' + }), name: ['', Validators.required], scraperConfiguration: '', symbolMapping: '' @@ -59,7 +63,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit { public countries: { [code: string]: { name: string; value: number }; }; - public historicalDataAsCsvString: string; public isBenchmark = false; public marketDataDetails: MarketData[] = []; public sectors: { @@ -78,7 +81,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit { @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams, private dataService: DataService, public dialogRef: MatDialogRef, - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + private snackBar: MatSnackBar ) {} public ngOnInit(): void { @@ -88,9 +92,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } public initialize() { - this.historicalDataAsCsvString = - AssetProfileDialog.HISTORICAL_DATA_TEMPLATE; - this.adminService .fetchAdminMarketDataBySymbol({ dataSource: this.data.dataSource, @@ -131,6 +132,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit { assetClass: this.assetProfile.assetClass ?? null, assetSubClass: this.assetProfile.assetSubClass ?? null, comment: this.assetProfile?.comment ?? '', + historicalData: { + csvString: AssetProfileDialog.HISTORICAL_DATA_TEMPLATE + }, name: this.assetProfile.name ?? this.assetProfile.symbol, scraperConfiguration: JSON.stringify( this.assetProfile?.scraperConfiguration ?? {} @@ -163,26 +167,46 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } public onImportHistoricalData() { - const marketData = csvToJson(this.historicalDataAsCsvString, { - dynamicTyping: true, - header: true, - skipEmptyLines: true - }).data; - - this.adminService - .postMarketData({ - dataSource: this.data.dataSource, - marketData: { - marketData: marketData.map(({ date, marketPrice }) => { - return { marketPrice, date: parseDate(date).toISOString() }; - }) - }, - symbol: this.data.symbol - }) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(() => { - this.initialize(); - }); + try { + const marketData = csvToJson( + this.assetProfileForm.controls['historicalData'].controls['csvString'] + .value, + { + dynamicTyping: true, + header: true, + skipEmptyLines: true + } + ).data; + + this.adminService + .postMarketData({ + dataSource: this.data.dataSource, + marketData: { + marketData: marketData.map(({ date, marketPrice }) => { + return { marketPrice, date: parseDate(date).toISOString() }; + }) + }, + symbol: this.data.symbol + }) + .pipe( + catchError(({ error, message }) => { + this.snackBar.open(`${error}: ${message[0]}`, undefined, { + duration: 3000 + }); + return EMPTY; + }), + takeUntil(this.unsubscribeSubject) + ) + .subscribe(() => { + this.initialize(); + }); + } catch { + this.snackBar.open( + $localize`Oops! Could not parse historical data.`, + undefined, + { duration: 3000 } + ); + } } public onMarketDataChanged(withRefresh: boolean = false) { diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index 755768209..72d673776 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -52,7 +52,7 @@ (marketDataChanged)="onMarketDataChanged($event)" > -
+
Historical Data (CSV) @@ -60,11 +60,9 @@ @@ -75,6 +73,7 @@ color="accent" mat-flat-button type="button" + [disabled]="!assetProfileForm.controls['historicalData']?.controls['csvString'].touched || assetProfileForm.controls['historicalData']?.controls['csvString']?.value === ''" (click)="onImportHistoricalData()" > Import @@ -179,13 +178,13 @@
- + Name
- + Asset Class @@ -198,7 +197,7 @@
- + Asset Sub Class diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts index b836e4066..0f9fdaaca 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts @@ -8,6 +8,7 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module'; import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module'; import { GfValueModule } from '@ghostfolio/ui/value'; @@ -28,6 +29,7 @@ import { AssetProfileDialog } from './asset-profile-dialog.component'; MatInputModule, MatMenuModule, MatSelectModule, + MatSnackBarModule, ReactiveFormsModule, TextFieldModule ],