Browse Source

Support migrating asset profile to MANUAL data source

pull/7376/head
Thomas Kaul 1 month ago
parent
commit
4f57225748
  1. 4
      apps/api/src/app/admin/admin.service.ts
  2. 17
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  3. 198
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

4
apps/api/src/app/admin/admin.service.ts

@ -242,9 +242,9 @@ export class AdminService {
}: Prisma.SymbolProfileUpdateInput }: Prisma.SymbolProfileUpdateInput
) { ) {
if ( if (
newSymbol &&
newDataSource && newDataSource &&
(newSymbol !== symbol || newDataSource !== dataSource) newSymbol &&
(newDataSource !== dataSource || newSymbol !== symbol)
) { ) {
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([ const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([
{ {

17
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -410,7 +410,8 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit {
const dialogRef = this.dialog.open< const dialogRef = this.dialog.open<
GfAssetProfileDialogComponent, GfAssetProfileDialogComponent,
AssetProfileDialogParams AssetProfileDialogParams,
AssetProfileIdentifier
>(GfAssetProfileDialogComponent, { >(GfAssetProfileDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
@ -428,15 +429,13 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit {
dialogRef dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe( .subscribe((newAssetProfileIdentifier) => {
(newAssetProfileIdentifier: AssetProfileIdentifier | undefined) => { if (newAssetProfileIdentifier) {
if (newAssetProfileIdentifier) { this.onOpenAssetProfileDialog(newAssetProfileIdentifier);
this.onOpenAssetProfileDialog(newAssetProfileIdentifier); } else {
} else { this.router.navigate(['.'], { relativeTo: this.route });
this.router.navigate(['.'], { relativeTo: this.route });
}
} }
); });
}); });
} }

198
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -5,6 +5,7 @@ import {
PROPERTY_IS_DATA_GATHERING_ENABLED PROPERTY_IS_DATA_GATHERING_ENABLED
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { UpdateAssetProfileDto } from '@ghostfolio/common/dtos'; import { UpdateAssetProfileDto } from '@ghostfolio/common/dtos';
import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { import {
canDeleteAssetProfile, canDeleteAssetProfile,
DATE_FORMAT, DATE_FORMAT,
@ -278,7 +279,10 @@ export class GfAssetProfileDialogComponent implements OnInit {
@Inject(MAT_DIALOG_DATA) protected data: AssetProfileDialogParams, @Inject(MAT_DIALOG_DATA) protected data: AssetProfileDialogParams,
private dataService: DataService, private dataService: DataService,
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
private dialogRef: MatDialogRef<GfAssetProfileDialogComponent>, private dialogRef: MatDialogRef<
GfAssetProfileDialogComponent,
AssetProfileIdentifier
>,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private notificationService: NotificationService, private notificationService: NotificationService,
private snackBar: MatSnackBar, private snackBar: MatSnackBar,
@ -469,57 +473,63 @@ export class GfAssetProfileDialogComponent implements OnInit {
} }
protected onConvertToManualDataSource() { protected onConvertToManualDataSource() {
const convertedAssetProfile: UpdateAssetProfileDto = { this.notificationService.confirm({
assetClass: this.assetProfile?.assetClass ?? undefined, confirmFn: () => {
assetSubClass: this.assetProfile?.assetSubClass ?? undefined, const newAssetProfileIdentifier: AssetProfileIdentifier = {
countries: this.assetProfile?.countries?.map(({ code, weight }) => { dataSource: DataSource.MANUAL,
return { code, weight }; symbol: crypto.randomUUID()
}), };
dataSource: DataSource.MANUAL,
holdings: this.assetProfile?.holdings?.map(
({ allocationInPercentage, name }) => {
return { allocationInPercentage, name };
}
),
name: this.assetProfile?.name ?? undefined,
sectors: this.assetProfile?.sectors?.map(({ name, weight }) => {
return { name, weight };
}),
symbol: crypto.randomUUID(),
url: this.assetProfile?.url ?? undefined
};
this.adminService const convertedAssetProfile: UpdateAssetProfileDto = {
.patchAssetProfile( assetClass: this.assetProfile?.assetClass ?? undefined,
{ assetSubClass: this.assetProfile?.assetSubClass ?? undefined,
dataSource: this.data.dataSource, countries: this.assetProfile?.countries?.map(({ code, weight }) => {
symbol: this.data.symbol return { code, weight };
}, }),
convertedAssetProfile dataSource: newAssetProfileIdentifier.dataSource,
) holdings: this.assetProfile?.holdings?.map(
.pipe( ({ allocationInPercentage, name }) => {
catchError(() => { return { allocationInPercentage, name };
this.snackBar.open(
'😞 ' +
$localize`An error occurred while converting the asset profile to ${DataSource.MANUAL}.`,
undefined,
{
duration: ms('3 seconds')
} }
); ),
name: this.assetProfile?.name ?? undefined,
return EMPTY; sectors: this.assetProfile?.sectors?.map(({ name, weight }) => {
}), return { name, weight };
takeUntilDestroyed(this.destroyRef) }),
) symbol: newAssetProfileIdentifier.symbol,
.subscribe(() => { url: this.assetProfile?.url ?? undefined
const newAssetProfileIdentifier = {
dataSource: convertedAssetProfile.dataSource,
symbol: convertedAssetProfile.symbol
}; };
this.dialogRef.close(newAssetProfileIdentifier); this.adminService
}); .patchAssetProfile(
{
dataSource: this.data.dataSource,
symbol: this.data.symbol
},
convertedAssetProfile
)
.pipe(
catchError(() => {
this.snackBar.open(
'😞 ' +
$localize`An error occurred while converting the asset profile to ${DataSource.MANUAL}.`,
undefined,
{
duration: ms('3 seconds')
}
);
return EMPTY;
}),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => {
this.dialogRef.close(newAssetProfileIdentifier);
});
},
confirmType: ConfirmationDialogType.Primary,
title: $localize`Do you really want to convert this asset profile to ${DataSource.MANUAL}?`
});
} }
protected onDeleteProfileData({ protected onDeleteProfileData({
@ -721,13 +731,16 @@ export class GfAssetProfileDialogComponent implements OnInit {
} }
protected async onSubmitAssetProfileIdentifierForm() { protected async onSubmitAssetProfileIdentifierForm() {
const newAssetProfileIdentifier =
this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value;
if (!newAssetProfileIdentifier?.dataSource) {
return;
}
const assetProfileIdentifier: UpdateAssetProfileDto = { const assetProfileIdentifier: UpdateAssetProfileDto = {
dataSource: dataSource: newAssetProfileIdentifier.dataSource,
this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value symbol: newAssetProfileIdentifier.symbol
?.dataSource ?? undefined,
symbol:
this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value
?.symbol ?? undefined
}; };
try { try {
@ -742,46 +755,47 @@ export class GfAssetProfileDialogComponent implements OnInit {
return; return;
} }
this.adminService this.notificationService.confirm({
.patchAssetProfile( confirmFn: () => {
{ this.adminService
dataSource: this.data.dataSource, .patchAssetProfile(
symbol: this.data.symbol {
}, dataSource: this.data.dataSource,
assetProfileIdentifier symbol: this.data.symbol
) },
.pipe( assetProfileIdentifier
catchError((error: HttpErrorResponse) => { )
if (error.status === StatusCodes.CONFLICT) { .pipe(
this.snackBar.open( catchError((error: HttpErrorResponse) => {
$localize`${assetProfileIdentifier.symbol} (${assetProfileIdentifier.dataSource}) is already in use.`, if (error.status === StatusCodes.CONFLICT) {
undefined, this.snackBar.open(
{ $localize`${assetProfileIdentifier.symbol} (${assetProfileIdentifier.dataSource}) is already in use.`,
duration: ms('3 seconds') undefined,
} {
); duration: ms('3 seconds')
} else { }
this.snackBar.open( );
$localize`An error occurred while updating to ${assetProfileIdentifier.symbol} (${assetProfileIdentifier.dataSource}).`, } else {
undefined, this.snackBar.open(
{ $localize`An error occurred while updating to ${assetProfileIdentifier.symbol} (${assetProfileIdentifier.dataSource}).`,
duration: ms('3 seconds') undefined,
{
duration: ms('3 seconds')
}
);
} }
);
}
return EMPTY; return EMPTY;
}), }),
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
) )
.subscribe(() => { .subscribe(() => {
const newAssetProfileIdentifier = { this.dialogRef.close(newAssetProfileIdentifier);
dataSource: assetProfileIdentifier.dataSource, });
symbol: assetProfileIdentifier.symbol },
}; confirmType: ConfirmationDialogType.Primary,
title: $localize`Do you really want to convert this asset profile to ${newAssetProfileIdentifier.symbol} (${newAssetProfileIdentifier.dataSource})?`
this.dialogRef.close(newAssetProfileIdentifier); });
});
} }
protected onTestMarketData() { protected onTestMarketData() {

Loading…
Cancel
Save