Browse Source

Task/improve type safety in asset profile dialog (#6722)

* Improve type safety
pull/6711/merge
Kenrick Tandrian 3 days ago
committed by GitHub
parent
commit
d5ff1bea56
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 299
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  2. 4
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

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

@ -87,6 +87,7 @@ import {
readerOutline,
serverOutline
} from 'ionicons/icons';
import { isBoolean } from 'lodash';
import ms from 'ms';
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
@ -129,25 +130,26 @@ export class GfAssetProfileDialogComponent implements OnInit {
)};123.45`;
@ViewChild('assetProfileFormElement')
assetProfileFormElement: ElementRef<HTMLFormElement>;
public readonly assetProfileFormElement: ElementRef<HTMLFormElement>;
public assetClassLabel: string;
public assetSubClassLabel: string;
protected assetClassLabel: string;
protected assetSubClassLabel: string;
public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass)
.map((id) => {
return { id, label: translate(id) } as AssetClassSelectorOption;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
protected readonly assetClassOptions: AssetClassSelectorOption[] =
Object.keys(AssetClass)
.map((id) => {
return { id, label: translate(id) } as AssetClassSelectorOption;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
public assetSubClassOptions: AssetClassSelectorOption[] = [];
public assetProfile: AdminMarketDataDetails['assetProfile'];
protected assetSubClassOptions: AssetClassSelectorOption[] = [];
protected assetProfile: AdminMarketDataDetails['assetProfile'];
public assetProfileForm = this.formBuilder.group({
assetClass: new FormControl<AssetClass>(undefined),
assetSubClass: new FormControl<AssetSubClass>(undefined),
protected readonly assetProfileForm = this.formBuilder.group({
assetClass: new FormControl<AssetClass | null>(null),
assetSubClass: new FormControl<AssetSubClass | null>(null),
comment: '',
countries: ['', jsonValidator()],
currency: '',
@ -156,11 +158,15 @@ export class GfAssetProfileDialogComponent implements OnInit {
}),
isActive: [true],
name: ['', Validators.required],
scraperConfiguration: this.formBuilder.group({
defaultMarketPrice: null,
headers: [JSON.stringify({}), jsonValidator()],
scraperConfiguration: this.formBuilder.group<
Omit<ScraperConfiguration, 'headers'> & {
headers: FormControl<string | null>;
}
>({
defaultMarketPrice: undefined,
headers: new FormControl(JSON.stringify({}), jsonValidator()),
locale: '',
mode: '',
mode: 'lazy',
selector: '',
url: ''
}),
@ -169,12 +175,11 @@ export class GfAssetProfileDialogComponent implements OnInit {
url: ''
});
public assetProfileIdentifierForm = this.formBuilder.group(
protected readonly assetProfileIdentifierForm = this.formBuilder.group(
{
assetProfileIdentifier: new FormControl<AssetProfileIdentifier>(
{ symbol: null, dataSource: null },
[Validators.required]
)
assetProfileIdentifier: new FormControl<
AssetProfileIdentifier | { dataSource: null; symbol: null }
>({ dataSource: null, symbol: null }, [Validators.required])
},
{
validators: (control) => {
@ -183,16 +188,15 @@ export class GfAssetProfileDialogComponent implements OnInit {
}
);
public benchmarks: Partial<SymbolProfile>[];
public canEditAssetProfile = true;
protected canEditAssetProfile = true;
public countries: {
protected countries: {
[code: string]: { name: string; value: number };
};
public currencies: string[] = [];
protected currencies: string[] = [];
public dateRangeOptions = [
protected readonly dateRangeOptions = [
{
label: $localize`Current week` + ' (' + $localize`WTD` + ')',
value: 'wtd'
@ -218,14 +222,14 @@ export class GfAssetProfileDialogComponent implements OnInit {
value: 'max'
}
];
public historicalDataItems: LineChartItem[];
public isBenchmark = false;
public isDataGatheringEnabled: boolean;
public isEditAssetProfileIdentifierMode = false;
public isUUID = isUUID;
public marketDataItems: MarketData[] = [];
public modeValues = [
protected historicalDataItems: LineChartItem[];
protected isBenchmark = false;
protected isDataGatheringEnabled: boolean;
protected isEditAssetProfileIdentifierMode = false;
protected readonly isUUID = isUUID;
protected marketDataItems: MarketData[] = [];
protected readonly modeValues = [
{
value: 'lazy',
viewValue: $localize`Lazy` + ' (' + $localize`end of day` + ')'
@ -236,20 +240,22 @@ export class GfAssetProfileDialogComponent implements OnInit {
}
];
public sectors: {
protected sectors: {
[name: string]: { name: string; value: number };
};
public user: User;
protected user: User;
private benchmarks: Partial<SymbolProfile>[];
public constructor(
public adminMarketDataService: AdminMarketDataService,
protected adminMarketDataService: AdminMarketDataService,
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams,
@Inject(MAT_DIALOG_DATA) protected data: AssetProfileDialogParams,
private dataService: DataService,
private destroyRef: DestroyRef,
public dialogRef: MatDialogRef<GfAssetProfileDialogComponent>,
private dialogRef: MatDialogRef<GfAssetProfileDialogComponent>,
private formBuilder: FormBuilder,
private notificationService: NotificationService,
private snackBar: MatSnackBar,
@ -264,7 +270,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
});
}
public get canSaveAssetProfileIdentifier() {
protected get canSaveAssetProfileIdentifier() {
return !this.assetProfileForm.dirty && this.canEditAssetProfile;
}
@ -277,8 +283,8 @@ export class GfAssetProfileDialogComponent implements OnInit {
this.initialize();
}
public initialize() {
this.historicalDataItems = undefined;
protected initialize() {
this.historicalDataItems = [];
this.adminService
.fetchAdminData()
@ -298,10 +304,13 @@ export class GfAssetProfileDialogComponent implements OnInit {
}
});
this.assetProfileForm
.get('assetClass')
.valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
this.assetProfileForm.controls.assetClass.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((assetClass) => {
if (!assetClass) {
return;
}
const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? [];
this.assetSubClassOptions = assetSubClasses
@ -313,7 +322,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
})
.sort((a, b) => a.label.localeCompare(b.label));
this.assetProfileForm.get('assetSubClass').setValue(null);
this.assetProfileForm.controls.assetSubClass.setValue(null);
this.changeDetectorRef.markForCheck();
});
@ -327,8 +336,10 @@ export class GfAssetProfileDialogComponent implements OnInit {
.subscribe(({ assetProfile, marketData }) => {
this.assetProfile = assetProfile;
this.assetClassLabel = translate(this.assetProfile?.assetClass);
this.assetSubClassLabel = translate(this.assetProfile?.assetSubClass);
this.assetClassLabel = translate(this.assetProfile?.assetClass ?? '');
this.assetSubClassLabel = translate(
this.assetProfile?.assetSubClass ?? ''
);
this.canEditAssetProfile = !isCurrency(
getCurrencyFromSymbol(this.data.symbol)
@ -350,7 +361,10 @@ export class GfAssetProfileDialogComponent implements OnInit {
this.marketDataItems = marketData;
this.sectors = {};
if (this.assetProfile?.countries?.length > 0) {
if (
this.assetProfile?.countries &&
this.assetProfile.countries.length > 0
) {
for (const { code, name, weight } of this.assetProfile.countries) {
this.countries[code] = {
name,
@ -359,7 +373,10 @@ export class GfAssetProfileDialogComponent implements OnInit {
}
}
if (this.assetProfile?.sectors?.length > 0) {
if (
this.assetProfile?.sectors &&
this.assetProfile.sectors.length > 0
) {
for (const { name, weight } of this.assetProfile.sectors) {
this.sectors[name] = {
name,
@ -377,12 +394,14 @@ export class GfAssetProfileDialogComponent implements OnInit {
return { code, weight };
}) ?? []
),
currency: this.assetProfile?.currency,
currency: this.assetProfile?.currency ?? null,
historicalData: {
csvString: GfAssetProfileDialogComponent.HISTORICAL_DATA_TEMPLATE
},
isActive: this.assetProfile?.isActive,
name: this.assetProfile.name ?? this.assetProfile.symbol,
isActive: isBoolean(this.assetProfile?.isActive)
? this.assetProfile.isActive
: null,
name: this.assetProfile.name ?? this.assetProfile.symbol ?? null,
scraperConfiguration: {
defaultMarketPrice:
this.assetProfile?.scraperConfiguration?.defaultMarketPrice ??
@ -410,7 +429,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
});
}
public onCancelEditAssetProfileIdentifierMode() {
protected onCancelEditAssetProfileIdentifierMode() {
this.isEditAssetProfileIdentifierMode = false;
if (this.canEditAssetProfile) {
@ -420,17 +439,20 @@ export class GfAssetProfileDialogComponent implements OnInit {
this.assetProfileIdentifierForm.reset();
}
public onClose() {
protected onClose() {
this.dialogRef.close();
}
public onDeleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) {
protected onDeleteProfileData({
dataSource,
symbol
}: AssetProfileIdentifier) {
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
this.dialogRef.close();
}
public onGatherProfileDataBySymbol({
protected onGatherProfileDataBySymbol({
dataSource,
symbol
}: AssetProfileIdentifier) {
@ -440,7 +462,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
.subscribe();
}
public onGatherSymbol({
protected onGatherSymbol({
dataSource,
range,
symbol
@ -453,13 +475,13 @@ export class GfAssetProfileDialogComponent implements OnInit {
.subscribe();
}
public onMarketDataChanged(withRefresh: boolean = false) {
protected onMarketDataChanged(withRefresh: boolean = false) {
if (withRefresh) {
this.initialize();
}
}
public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
protected onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
this.dataService
.postBenchmark({ dataSource, symbol })
.pipe(takeUntilDestroyed(this.destroyRef))
@ -472,50 +494,48 @@ export class GfAssetProfileDialogComponent implements OnInit {
});
}
public onSetEditAssetProfileIdentifierMode() {
protected onSetEditAssetProfileIdentifierMode() {
this.isEditAssetProfileIdentifierMode = true;
this.assetProfileForm.disable();
}
public async onSubmitAssetProfileForm() {
let countries = [];
let scraperConfiguration: ScraperConfiguration = {
protected async onSubmitAssetProfileForm() {
let countries: Prisma.InputJsonArray = [];
let scraperConfiguration: Prisma.InputJsonObject | undefined = {
selector: '',
url: ''
};
let sectors = [];
let symbolMapping = {};
let sectors: Prisma.InputJsonArray = [];
let symbolMapping: Record<string, string> = {};
try {
countries = JSON.parse(this.assetProfileForm.get('countries').value);
countries = JSON.parse(
this.assetProfileForm.controls.countries.value ?? '[]'
) as Prisma.InputJsonArray;
} catch {}
try {
scraperConfiguration = {
defaultMarketPrice:
(this.assetProfileForm.controls['scraperConfiguration'].controls[
'defaultMarketPrice'
].value as number) || undefined,
this.assetProfileForm.controls.scraperConfiguration.controls
.defaultMarketPrice?.value ?? undefined,
headers: JSON.parse(
this.assetProfileForm.controls['scraperConfiguration'].controls[
'headers'
].value
),
this.assetProfileForm.controls.scraperConfiguration.controls.headers
.value ?? '{}'
) as Record<string, string>,
locale:
this.assetProfileForm.controls['scraperConfiguration'].controls[
'locale'
].value || undefined,
mode: this.assetProfileForm.controls['scraperConfiguration'].controls[
'mode'
].value as ScraperConfiguration['mode'],
this.assetProfileForm.controls.scraperConfiguration.controls.locale
?.value ?? undefined,
mode:
this.assetProfileForm.controls.scraperConfiguration.controls.mode
?.value ?? undefined,
selector:
this.assetProfileForm.controls['scraperConfiguration'].controls[
'selector'
].value,
url: this.assetProfileForm.controls['scraperConfiguration'].controls[
'url'
].value
this.assetProfileForm.controls.scraperConfiguration.controls.selector
.value ?? '',
url:
this.assetProfileForm.controls.scraperConfiguration.controls.url
.value ?? ''
};
if (!scraperConfiguration.selector || !scraperConfiguration.url) {
@ -536,28 +556,32 @@ export class GfAssetProfileDialogComponent implements OnInit {
}
try {
sectors = JSON.parse(this.assetProfileForm.get('sectors').value);
sectors = JSON.parse(
this.assetProfileForm.controls.sectors.value ?? '[]'
) as Prisma.InputJsonArray;
} catch {}
try {
symbolMapping = JSON.parse(
this.assetProfileForm.get('symbolMapping').value
);
this.assetProfileForm.controls.symbolMapping.value ?? '{}'
) as Record<string, string>;
} catch {}
const assetProfile: UpdateAssetProfileDto = {
countries,
scraperConfiguration,
sectors,
symbolMapping,
assetClass: this.assetProfileForm.get('assetClass').value,
assetSubClass: this.assetProfileForm.get('assetSubClass').value,
comment: this.assetProfileForm.get('comment').value || null,
currency: this.assetProfileForm.get('currency').value,
isActive: this.assetProfileForm.get('isActive').value,
name: this.assetProfileForm.get('name').value,
scraperConfiguration:
scraperConfiguration as unknown as Prisma.InputJsonObject,
url: this.assetProfileForm.get('url').value || null
assetClass: this.assetProfileForm.controls.assetClass.value ?? undefined,
assetSubClass:
this.assetProfileForm.controls.assetSubClass.value ?? undefined,
comment: this.assetProfileForm.controls.comment.value ?? undefined,
currency: this.assetProfileForm.controls.currency.value ?? undefined,
isActive: isBoolean(this.assetProfileForm.controls.isActive.value)
? this.assetProfileForm.controls.isActive.value
: undefined,
name: this.assetProfileForm.controls.name.value ?? undefined,
url: this.assetProfileForm.controls.url.value ?? undefined
};
try {
@ -600,7 +624,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
this.initialize();
},
error: (error) => {
error: (error: HttpErrorResponse) => {
console.error($localize`Could not save asset profile`, error);
this.snackBar.open(
@ -614,12 +638,14 @@ export class GfAssetProfileDialogComponent implements OnInit {
});
}
public async onSubmitAssetProfileIdentifierForm() {
protected async onSubmitAssetProfileIdentifierForm() {
const assetProfileIdentifier: UpdateAssetProfileDto = {
dataSource: this.assetProfileIdentifierForm.get('assetProfileIdentifier')
.value.dataSource,
symbol: this.assetProfileIdentifierForm.get('assetProfileIdentifier')
.value.symbol
dataSource:
this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value
?.dataSource ?? undefined,
symbol:
this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value
?.symbol ?? undefined
};
try {
@ -676,38 +702,33 @@ export class GfAssetProfileDialogComponent implements OnInit {
});
}
public onTestMarketData() {
protected onTestMarketData() {
this.adminService
.testMarketData({
dataSource: this.data.dataSource,
scraperConfiguration: {
defaultMarketPrice: this.assetProfileForm.controls[
'scraperConfiguration'
].controls['defaultMarketPrice'].value as number,
defaultMarketPrice:
this.assetProfileForm.controls.scraperConfiguration.controls
.defaultMarketPrice?.value,
headers: JSON.parse(
this.assetProfileForm.controls['scraperConfiguration'].controls[
'headers'
].value
),
this.assetProfileForm.controls.scraperConfiguration.controls.headers
.value ?? '{}'
) as Record<string, string>,
locale:
this.assetProfileForm.controls['scraperConfiguration'].controls[
'locale'
].value || undefined,
mode: this.assetProfileForm.controls['scraperConfiguration'].controls[
'mode'
].value,
this.assetProfileForm.controls.scraperConfiguration.controls.locale
?.value || undefined,
mode: this.assetProfileForm.controls.scraperConfiguration.controls
.mode?.value,
selector:
this.assetProfileForm.controls['scraperConfiguration'].controls[
'selector'
].value,
url: this.assetProfileForm.controls['scraperConfiguration'].controls[
'url'
].value
this.assetProfileForm.controls.scraperConfiguration.controls
.selector.value,
url: this.assetProfileForm.controls.scraperConfiguration.controls.url
.value
},
symbol: this.data.symbol
})
.pipe(
catchError(({ error }) => {
catchError(({ error }: HttpErrorResponse) => {
this.notificationService.alert({
message: error?.message,
title: $localize`Error`
@ -723,26 +744,26 @@ export class GfAssetProfileDialogComponent implements OnInit {
' ' +
price +
' ' +
this.assetProfileForm.get('currency').value
this.assetProfileForm.controls.currency.value
});
});
}
public onToggleIsActive({ checked }: MatCheckboxChange) {
protected onToggleIsActive({ checked }: MatCheckboxChange) {
if (checked) {
this.assetProfileForm.get('isActive')?.setValue(true);
this.assetProfileForm.controls.isActive.setValue(true);
} else {
this.assetProfileForm.get('isActive')?.setValue(false);
this.assetProfileForm.controls.isActive.setValue(false);
}
if (checked === this.assetProfile.isActive) {
this.assetProfileForm.get('isActive')?.markAsPristine();
this.assetProfileForm.controls.isActive.markAsPristine();
} else {
this.assetProfileForm.get('isActive')?.markAsDirty();
this.assetProfileForm.controls.isActive.markAsDirty();
}
}
public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
protected onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
this.dataService
.deleteBenchmark({ dataSource, symbol })
.pipe(takeUntilDestroyed(this.destroyRef))
@ -755,15 +776,15 @@ export class GfAssetProfileDialogComponent implements OnInit {
});
}
public onTriggerSubmitAssetProfileForm() {
protected onTriggerSubmitAssetProfileForm() {
if (this.assetProfileForm.valid) {
this.onSubmitAssetProfileForm();
}
}
private isNewSymbolValid(control: AbstractControl): ValidationErrors {
private isNewSymbolValid(control: AbstractControl): ValidationErrors | null {
const currentAssetProfileIdentifier: AssetProfileIdentifier | undefined =
control.get('assetProfileIdentifier').value;
control.get('assetProfileIdentifier')?.value;
if (
currentAssetProfileIdentifier?.dataSource === this.data?.dataSource &&
@ -773,5 +794,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
equalsPreviousProfileIdentifier: true
};
}
return null;
}
}

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

@ -419,11 +419,11 @@
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Url</mat-label>
<input formControlName="url" matInput type="text" />
@if (assetProfileForm.get('url').value) {
@if (assetProfileForm.controls.url.value) {
<gf-entity-logo
class="mr-3"
matSuffix
[url]="assetProfileForm.get('url').value"
[url]="assetProfileForm.controls.url.value"
/>
}
</mat-form-field>

Loading…
Cancel
Save