Browse Source

Extend asset profile dialog form (#2535)

* Extend asset profile dialog form

* Update changelog
pull/2543/head
Aldrin 1 year ago
committed by GitHub
parent
commit
0af37ca1d7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 6
      apps/api/src/app/admin/admin.service.ts
  3. 16
      apps/api/src/app/admin/update-asset-profile.dto.ts
  4. 12
      apps/api/src/services/symbol-profile/symbol-profile.service.ts
  5. 36
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  6. 42
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  7. 2
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts
  8. 12
      apps/client/src/app/services/admin.service.ts

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added support to edit the name, asset class and asset sub class of asset profiles with `MANUAL` data source in the asset profile details dialog of the admin control panel
### Changed ### Changed
- Improved the style and wording of the position detail dialog - Improved the style and wording of the position detail dialog

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

@ -303,15 +303,21 @@ export class AdminService {
} }
public async patchAssetProfileData({ public async patchAssetProfileData({
assetClass,
assetSubClass,
comment, comment,
dataSource, dataSource,
name,
scraperConfiguration, scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) { }: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
await this.symbolProfileService.updateSymbolProfile({ await this.symbolProfileService.updateSymbolProfile({
assetClass,
assetSubClass,
comment, comment,
dataSource, dataSource,
name,
scraperConfiguration, scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping

16
apps/api/src/app/admin/update-asset-profile.dto.ts

@ -1,11 +1,23 @@
import { Prisma } from '@prisma/client'; import { AssetClass, AssetSubClass, Prisma } from '@prisma/client';
import { IsObject, IsOptional, IsString } from 'class-validator'; import { IsEnum, IsObject, IsOptional, IsString } from 'class-validator';
export class UpdateAssetProfileDto { export class UpdateAssetProfileDto {
@IsEnum(AssetClass, { each: true })
@IsOptional()
assetClass?: AssetClass;
@IsEnum(AssetSubClass, { each: true })
@IsOptional()
assetSubClass?: AssetSubClass;
@IsString() @IsString()
@IsOptional() @IsOptional()
comment?: string; comment?: string;
@IsString()
@IsOptional()
name?: string;
@IsObject() @IsObject()
@IsOptional() @IsOptional()
scraperConfiguration?: Prisma.InputJsonObject; scraperConfiguration?: Prisma.InputJsonObject;

12
apps/api/src/services/symbol-profile/symbol-profile.service.ts

@ -86,14 +86,24 @@ export class SymbolProfileService {
} }
public updateSymbolProfile({ public updateSymbolProfile({
assetClass,
assetSubClass,
comment, comment,
dataSource, dataSource,
name,
scraperConfiguration, scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) { }: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
return this.prismaService.symbolProfile.update({ return this.prismaService.symbolProfile.update({
data: { comment, scraperConfiguration, symbolMapping }, data: {
assetClass,
assetSubClass,
comment,
name,
scraperConfiguration,
symbolMapping
},
where: { dataSource_symbol: { dataSource, symbol } } where: { dataSource_symbol: { dataSource, symbol } }
}); });
} }

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

@ -6,7 +6,7 @@ import {
OnDestroy, OnDestroy,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
@ -17,7 +17,12 @@ import {
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { MarketData, SymbolProfile } from '@prisma/client'; import {
AssetClass,
AssetSubClass,
MarketData,
SymbolProfile
} from '@prisma/client';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
import { parse as csvToJson } from 'papaparse'; import { parse as csvToJson } from 'papaparse';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -33,14 +38,23 @@ import { AssetProfileDialogParams } from './interfaces/interfaces';
styleUrls: ['./asset-profile-dialog.component.scss'] styleUrls: ['./asset-profile-dialog.component.scss']
}) })
export class AssetProfileDialog implements OnDestroy, OnInit { export class AssetProfileDialog implements OnDestroy, OnInit {
public assetClass: string; public assetProfileClass: string;
public assetClasses = Object.keys(AssetClass).map((assetClass) => {
return { id: assetClass, label: translate(assetClass) };
});
public assetSubClasses = Object.keys(AssetSubClass).map((assetSubClass) => {
return { id: assetSubClass, label: translate(assetSubClass) };
});
public assetProfile: AdminMarketDataDetails['assetProfile']; public assetProfile: AdminMarketDataDetails['assetProfile'];
public assetProfileForm = this.formBuilder.group({ public assetProfileForm = this.formBuilder.group({
assetClass: new FormControl<AssetClass>(undefined),
assetSubClass: new FormControl<AssetSubClass>(undefined),
comment: '', comment: '',
name: ['', Validators.required],
scraperConfiguration: '', scraperConfiguration: '',
symbolMapping: '' symbolMapping: ''
}); });
public assetSubClass: string; public assetProfileSubClass: string;
public benchmarks: Partial<SymbolProfile>[]; public benchmarks: Partial<SymbolProfile>[];
public countries: { public countries: {
[code: string]: { name: string; value: number }; [code: string]: { name: string; value: number };
@ -86,8 +100,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
.subscribe(({ assetProfile, marketData }) => { .subscribe(({ assetProfile, marketData }) => {
this.assetProfile = assetProfile; this.assetProfile = assetProfile;
this.assetClass = translate(this.assetProfile?.assetClass); this.assetProfileClass = translate(this.assetProfile?.assetClass);
this.assetSubClass = translate(this.assetProfile?.assetSubClass); this.assetProfileSubClass = translate(this.assetProfile?.assetSubClass);
this.countries = {}; this.countries = {};
this.isBenchmark = this.benchmarks.some(({ id }) => { this.isBenchmark = this.benchmarks.some(({ id }) => {
return id === this.assetProfile.id; return id === this.assetProfile.id;
@ -114,6 +128,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
} }
this.assetProfileForm.setValue({ this.assetProfileForm.setValue({
name: this.assetProfile.name,
assetClass: this.assetProfile.assetClass,
assetSubClass: this.assetProfile.assetSubClass,
comment: this.assetProfile?.comment ?? '', comment: this.assetProfile?.comment ?? '',
scraperConfiguration: JSON.stringify( scraperConfiguration: JSON.stringify(
this.assetProfile?.scraperConfiguration ?? {} this.assetProfile?.scraperConfiguration ?? {}
@ -204,9 +221,12 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
} catch {} } catch {}
const assetProfileData: UpdateAssetProfileDto = { const assetProfileData: UpdateAssetProfileDto = {
assetClass: this.assetProfileForm.controls['assetClass'].value,
assetSubClass: this.assetProfileForm.controls['assetSubClass'].value,
comment: this.assetProfileForm.controls['comment'].value ?? null,
name: this.assetProfileForm.controls['name'].value,
scraperConfiguration, scraperConfiguration,
symbolMapping, symbolMapping
comment: this.assetProfileForm.controls['comment'].value ?? null
}; };
this.adminService this.adminService

42
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -112,7 +112,11 @@
> >
</div> </div>
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value i18n size="medium" [hidden]="!assetClass" [value]="assetClass" <gf-value
i18n
size="medium"
[hidden]="!assetProfileClass"
[value]="assetProfileClass"
>Asset Class</gf-value >Asset Class</gf-value
> >
</div> </div>
@ -120,8 +124,8 @@
<gf-value <gf-value
i18n i18n
size="medium" size="medium"
[hidden]="!assetSubClass" [hidden]="!assetProfileSubClass"
[value]="assetSubClass" [value]="assetProfileSubClass"
>Asset Sub Class</gf-value >Asset Sub Class</gf-value
> >
</div> </div>
@ -174,6 +178,38 @@
</ng-template> </ng-template>
</ng-container> </ng-container>
</div> </div>
<div *ngIf="assetProfile?.dataSource === 'MANUAL'" class="mt-3">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Name</mat-label>
<input formControlName="name" matInput type="text" />
</mat-form-field>
</div>
<div *ngIf="assetProfile?.dataSource === 'MANUAL'" class="mt-3">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Asset Class</mat-label>
<mat-select formControlName="assetClass">
<mat-option [value]="null"></mat-option>
<mat-option
*ngFor="let assetClass of assetClasses"
[value]="assetClass.id"
>{{ assetClass.label }}</mat-option
>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="assetProfile?.dataSource === 'MANUAL'" class="mt-3">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Asset Sub Class</mat-label>
<mat-select formControlName="assetSubClass">
<mat-option [value]="null"></mat-option>
<mat-option
*ngFor="let assetSubClass of assetSubClasses"
[value]="assetSubClass.id"
>{{ assetSubClass.label }}</mat-option
>
</mat-select>
</mat-form-field>
</div>
<div class="d-flex my-3"> <div class="d-flex my-3">
<div class="w-50"> <div class="w-50">
<mat-checkbox <mat-checkbox

2
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts

@ -7,6 +7,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module'; 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 { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value'; import { GfValueModule } from '@ghostfolio/ui/value';
@ -26,6 +27,7 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
MatDialogModule, MatDialogModule,
MatInputModule, MatInputModule,
MatMenuModule, MatMenuModule,
MatSelectModule,
ReactiveFormsModule, ReactiveFormsModule,
TextFieldModule TextFieldModule
], ],

12
apps/client/src/app/services/admin.service.ts

@ -203,15 +203,25 @@ export class AdminService {
} }
public patchAssetProfile({ public patchAssetProfile({
assetClass,
assetSubClass,
comment, comment,
dataSource, dataSource,
name,
scraperConfiguration, scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}: UniqueAsset & UpdateAssetProfileDto) { }: UniqueAsset & UpdateAssetProfileDto) {
return this.http.patch<EnhancedSymbolProfile>( return this.http.patch<EnhancedSymbolProfile>(
`/api/v1/admin/profile-data/${dataSource}/${symbol}`, `/api/v1/admin/profile-data/${dataSource}/${symbol}`,
{ comment, scraperConfiguration, symbolMapping } {
assetClass,
assetSubClass,
comment,
name,
scraperConfiguration,
symbolMapping
}
); );
} }

Loading…
Cancel
Save