Feature/add validation for import (#415)

* Valid data types
* Maximum number of orders
* Data provider service returns data for the dataSource / symbol pair
This commit is contained in:
Thomas Kaul
2021-10-12 22:19:32 +02:00
committed by GitHub
parent b9f0a57522
commit 93dcbeb6c7
17 changed files with 171 additions and 30 deletions

View File

@@ -101,7 +101,7 @@ export class HttpResponseInterceptor implements HttpInterceptor {
}
}
return throwError('');
return throwError(error);
})
);
}

View File

@@ -35,4 +35,4 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class CreateOrUpdateTransactionDialogModule {}
export class GfCreateOrUpdateTransactionDialogModule {}

View File

@@ -1,5 +1,5 @@
import { User } from '@ghostfolio/common/interfaces';
import { Account, Order } from '@prisma/client';
import { Order } from '@prisma/client';
export interface CreateOrUpdateTransactionDialogParams {
accountId: string;

View File

@@ -0,0 +1,36 @@
import {
ChangeDetectionStrategy,
Component,
Inject,
OnDestroy
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Subject } from 'rxjs';
import { ImportTransactionDialogParams } from './interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-import-transaction-dialog',
styleUrls: ['./import-transaction-dialog.scss'],
templateUrl: 'import-transaction-dialog.html'
})
export class ImportTransactionDialog implements OnDestroy {
private unsubscribeSubject = new Subject<void>();
public constructor(
@Inject(MAT_DIALOG_DATA) public data: ImportTransactionDialogParams,
public dialogRef: MatDialogRef<ImportTransactionDialog>
) {}
public ngOnInit() {}
public onCancel(): void {
this.dialogRef.close();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

View File

@@ -0,0 +1,23 @@
<gf-dialog-header
mat-dialog-title
title="Import Transactions Error"
[deviceType]="data.deviceType"
(closeButtonClicked)="onCancel()"
></gf-dialog-header>
<div class="flex-grow-1" mat-dialog-content>
<ul class="list-unstyled">
<li *ngFor="let message of data.messages" class="d-flex">
<div class="align-items-center d-flex px-2">
<ion-icon name="warning-outline"></ion-icon>
</div>
<div>{{ message }}</div>
</li>
</ul>
</div>
<gf-dialog-footer
mat-dialog-actions
[deviceType]="data.deviceType"
(closeButtonClicked)="onCancel()"
></gf-dialog-footer>

View File

@@ -0,0 +1,23 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { ImportTransactionDialog } from './import-transaction-dialog.component';
@NgModule({
declarations: [ImportTransactionDialog],
exports: [],
imports: [
CommonModule,
GfDialogFooterModule,
GfDialogHeaderModule,
MatButtonModule,
MatDialogModule
],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfImportTransactionDialogModule {}

View File

@@ -0,0 +1,4 @@
export interface ImportTransactionDialogParams {
deviceType: string;
messages: string[];
}

View File

@@ -16,6 +16,7 @@ import { EMPTY, Subject, Subscription } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators';
import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog/create-or-update-transaction-dialog.component';
import { ImportTransactionDialog } from './import-transaction-dialog/import-transaction-dialog.component';
@Component({
selector: 'gf-transactions-page',
@@ -23,6 +24,7 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
styleUrls: ['./transactions-page.scss']
})
export class TransactionsPageComponent implements OnDestroy, OnInit {
public defaultAccountId: string;
public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean;
@@ -93,6 +95,10 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
if (state?.user) {
this.user = state.user;
this.defaultAccountId = this.user?.accounts.find((account) => {
return account.isDefault;
})?.id;
this.hasPermissionToCreateOrder = hasPermission(
this.user.permissions,
permissions.createOrder
@@ -175,7 +181,9 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
this.dataService
.postImport({
orders: content.orders
orders: content.orders.map((order) => {
return { ...order, accountId: this.defaultAccountId };
})
})
.pipe(
catchError((error) => {
@@ -195,7 +203,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
}
});
} catch (error) {
this.handleImportError(error);
this.handleImportError({ error: { message: ['Unexpected format'] } });
}
};
};
@@ -281,20 +289,23 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
a.click();
}
private handleImportError(aError: unknown) {
console.error(aError);
this.snackBar.open('❌ Oops, something went wrong...');
private handleImportError(aError: any) {
this.snackBar.dismiss();
this.dialog.open(ImportTransactionDialog, {
data: {
deviceType: this.deviceType,
messages: aError?.error?.message
},
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
}
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
data: {
transaction: {
accountId:
aTransaction?.accountId ??
this.user?.accounts.find((account) => {
return account.isDefault;
})?.id,
accountId: aTransaction?.accountId ?? this.defaultAccountId,
currency: aTransaction?.currency ?? null,
dataSource: aTransaction?.dataSource ?? null,
date: new Date(),

View File

@@ -5,7 +5,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
import { RouterModule } from '@angular/router';
import { GfTransactionsTableModule } from '@ghostfolio/client/components/transactions-table/transactions-table.module';
import { CreateOrUpdateTransactionDialogModule } from './create-or-update-transaction-dialog/create-or-update-transaction-dialog.module';
import { GfCreateOrUpdateTransactionDialogModule } from './create-or-update-transaction-dialog/create-or-update-transaction-dialog.module';
import { GfImportTransactionDialogModule } from './import-transaction-dialog/import-transaction-dialog.module';
import { TransactionsPageRoutingModule } from './transactions-page-routing.module';
import { TransactionsPageComponent } from './transactions-page.component';
@@ -14,7 +15,8 @@ import { TransactionsPageComponent } from './transactions-page.component';
exports: [],
imports: [
CommonModule,
CreateOrUpdateTransactionDialogModule,
GfCreateOrUpdateTransactionDialogModule,
GfImportTransactionDialogModule,
GfTransactionsTableModule,
MatButtonModule,
MatSnackBarModule,

View File

@@ -39,18 +39,13 @@ import { cloneDeep } from 'lodash';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SettingsStorageService } from './settings-storage.service';
@Injectable({
providedIn: 'root'
})
export class DataService {
private info: InfoItem;
public constructor(
private http: HttpClient,
private settingsStorageService: SettingsStorageService
) {}
public constructor(private http: HttpClient) {}
public createCheckoutSession({
couponId,