IMP-125: Fix optional value of thrift form (#288)

This commit is contained in:
Rinat Arsaev 2023-11-20 23:27:41 +07:00 committed by GitHub
parent 6ed90e1cec
commit 26c6944280
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 123 additions and 131 deletions

8
package-lock.json generated
View File

@ -26,7 +26,7 @@
"@vality/domain-proto": "2.0.1-45b0719.0",
"@vality/fistful-proto": "2.0.1-3b9a0a7.0",
"@vality/magista-proto": "2.0.2-4383410.0",
"@vality/ng-core": "16.2.1-pr-49-6a1a300.0",
"@vality/ng-core": "16.2.1-pr-49-05562a6.0",
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/thrift-ts": "2.4.1-8ad5123.0",
@ -5966,9 +5966,9 @@
"integrity": "sha512-kAiKSTvof+jFuNkQKyAsc2s+Br2NXPWAyKuD0f7mQIk9HrP8uHsKJya5KxdOdng97JYe0MSUlx7seQxWmCgYfA=="
},
"node_modules/@vality/ng-core": {
"version": "16.2.1-pr-49-6a1a300.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-49-6a1a300.0.tgz",
"integrity": "sha512-ZIJtxfBqfqyMbjtt1OUJwEzGJxI2sIBYVldn/LXKB1WqK3rICfmwQL1tannuVD1SD8z41sW5cC/70W/ax/FZxQ==",
"version": "16.2.1-pr-49-05562a6.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-49-05562a6.0.tgz",
"integrity": "sha512-QUkFbN+e2BgfWnyVpaEVHqJJotcmBLgaoznoJo+smGz8JBje0UdGUxX8lroqRMJ2flZ8gprEO9iqBRZwGqpN5A==",
"dependencies": {
"@angular/material-date-fns-adapter": "^16.0.0",
"@ng-matero/extensions": "^16.0.0",

View File

@ -34,7 +34,7 @@
"@vality/domain-proto": "2.0.1-45b0719.0",
"@vality/fistful-proto": "2.0.1-3b9a0a7.0",
"@vality/magista-proto": "2.0.2-4383410.0",
"@vality/ng-core": "16.2.1-pr-49-6a1a300.0",
"@vality/ng-core": "16.2.1-pr-49-05562a6.0",
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/thrift-ts": "2.4.1-8ad5123.0",

View File

@ -1,10 +1,11 @@
<div style="display: flex; flex-direction: column; gap: 8px">
<div gdColumns="1fr 1fr" gdGap="24px">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px">
<v-select-field
[formControl]="typesControl"
[options]="options$ | async"
label="Object types"
multiple
style="overflow: auto"
></v-select-field>
<mat-form-field>
<mat-label>Search</mat-label>

View File

@ -59,7 +59,9 @@ export class DomainInfoComponent {
) {}
edit() {
void this.router.navigate(['domain', 'edit', JSON.stringify(this.objWithRef.ref)]);
void this.router.navigate(['domain', 'edit'], {
queryParams: { ref: JSON.stringify(this.objWithRef.ref) },
});
}
delete() {

View File

@ -61,7 +61,7 @@ export class DomainObjModificationComponent implements OnInit {
this.domainObjModService.object$.pipe(first(), untilDestroyed(this)).subscribe((object) => {
if (
this.modifiedDomainObjectService.domainObject &&
this.route.snapshot.params.ref === this.modifiedDomainObjectService.ref
this.route.snapshot.queryParams.ref === this.modifiedDomainObjectService.ref
) {
this.control.setValue(this.modifiedDomainObjectService.domainObject);
} else {
@ -71,8 +71,13 @@ export class DomainObjModificationComponent implements OnInit {
}
reviewChanges() {
this.modifiedDomainObjectService.update(this.control.value, this.route.snapshot.params.ref);
void this.router.navigate(['domain', 'edit', this.route.snapshot.params.ref, 'review']);
this.modifiedDomainObjectService.update(
this.control.value,
this.route.snapshot.queryParams.ref,
);
void this.router.navigate(['domain', 'review'], {
queryParams: { ref: this.route.snapshot.queryParams.ref },
});
}
backToDomain() {

View File

@ -76,6 +76,8 @@ export class DomainObjReviewComponent {
}
back() {
void this.router.navigate(['domain', 'edit', this.route.snapshot.params.ref]);
void this.router.navigate(['domain', 'edit'], {
queryParams: { ref: this.route.snapshot.queryParams.ref },
});
}
}

View File

@ -26,11 +26,11 @@ import { ROUTING_CONFIG } from './routing-config';
component: DomainObjCreationComponent,
},
{
path: 'edit/:ref',
path: 'edit',
component: DomainObjModificationComponent,
},
{
path: 'edit/:ref/review',
path: 'review',
component: DomainObjReviewComponent,
},
],

View File

@ -25,7 +25,7 @@ export class DomainObjModificationService {
shareReplay({ refCount: true, bufferSize: 1 }),
);
private ref$ = this.route.params.pipe(
private ref$ = this.route.queryParams.pipe(
map(({ ref }) => {
try {
return JSON.parse(ref as string) as Reference;

View File

@ -44,10 +44,10 @@
<button
[disabled]="!(selected$ | async)?.length"
color="primary"
mat-button
mat-raised-button
(click)="createPaymentAdjustment()"
>
Create payment adjustment
Create adjustment
</button>
</cc-payments-table>
</cc-page-layout>

View File

@ -1,5 +1,5 @@
<v-dialog [progress]="!!(progress$ | async)" title="Create payout">
<div [formGroup]="control" gdColumns="1fr" gdGap="16px">
<div [formGroup]="control" style="display: grid; grid-template-columns: 1fr; gap: 16px">
<cc-merchant-field formControlName="partyId" required></cc-merchant-field>
<cc-shop-field
[partyId]="this.control.value.partyId"

View File

@ -1,5 +1,5 @@
<v-dialog gdColumn="1fr" title="Repair by scenario">
<div gdGap="16px">
<v-dialog style="display: grid; grid-template-columns: 1fr" title="Repair by scenario">
<div style="display: grid; gap: 16px">
<mat-form-field>
<mat-label>Type</mat-label>
<mat-select [formControl]="nsControl">
@ -8,7 +8,10 @@
</mat-select>
</mat-form-field>
<mat-radio-group [formControl]="typeControl" gdColumns="1fr 1fr" gdGap="8px">
<mat-radio-group
[formControl]="typeControl"
style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px"
>
<mat-radio-button [value]="typesEnum.Same">Same</mat-radio-button>
<mat-radio-button [value]="typesEnum.Different">Different</mat-radio-button>
</mat-radio-group>

View File

@ -1,6 +1,9 @@
<cc-page-layout title="Repairing">
<mat-card>
<mat-card-content [formGroup]="filters" gdColumns="1fr 1fr 1fr" gdGap="16px">
<mat-card-content
[formGroup]="filters"
style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px"
>
<v-list-field formControlName="ids" label="IDs"></v-list-field>
<mat-form-field>
<mat-label>Namespace</mat-label>

View File

@ -1,6 +1,9 @@
<v-dialog [progress]="progress$ | async" title="Create Adjustment">
<div gdColumn="1fr" gdGap="16px">
<mat-radio-group [formControl]="typeControl" gdColumns="1fr 1fr" gdGap="8px">
<div style="display: grid; grid-template-columns: 1fr; gap: 16px">
<mat-radio-group
[formControl]="typeControl"
style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px"
>
<mat-radio-button [value]="0">Use ID from withdrawal</mat-radio-button>
<mat-radio-button [value]="1">Generate ID</mat-radio-button>
</mat-radio-group>

View File

@ -6,7 +6,7 @@
: dialogData.chargebacks.length + ' chargebacks'
}} status"
>
<div gdColumn="1fr" gdGap="16px">
<div style="display: grid; grid-template-columns: 1fr; gap: 16px">
<mat-form-field style="width: 100%">
<mat-label>Action</mat-label>
<mat-select [formControl]="actionControl">

View File

@ -1,14 +1,20 @@
<ng-container *ngIf="view?.items$ | async as items">
<div *ngIf="!(view.isValue$ | async); else onlyValue" gdColumns="1fr" gdGap="16px">
<div
*ngIf="!(view.isValue$ | async); else onlyValue"
style="display: grid; grid-template-columns: 1fr; gap: 16px"
>
<div
*ngIf="(view.leaves$ | async)?.length as count"
[gdColumns]="count === 1 ? '1fr' : count === 2 ? '1fr 1fr' : '1fr 1fr 1fr'"
gdGap="16px"
[ngStyle]="{
display: 'grid',
gap: '16px',
'grid-template-columns':
count === 1 ? '1fr' : count === 2 ? '1fr 1fr' : '1fr 1fr 1fr'
}"
>
<div
*ngFor="let item of view.leaves$ | async; let i = index"
gdGap="8px"
gdRows="auto 1fr"
style="display: grid; grid-template-rows: auto 1fr; gap: 8px"
>
<cc-key [keys]="item.path$ | async" class="mat-caption mat-secondary-text"></cc-key>
@ -30,8 +36,12 @@
<ng-container *ngFor="let item of view?.nodes$ | async; let idx = index">
<mat-divider *ngIf="idx > 0 || (view.leaves$ | async)?.length"></mat-divider>
<div
[gdColumns]="((item.current$ | async)?.isNumberKey$ | async) ? 'auto 1fr' : '1fr'"
gdGap="16px"
[ngStyle]="{
display: 'grid',
'grid-template-columns':
((item.current$ | async)?.isNumberKey$ | async) ? 'auto 1fr' : '1fr',
gap: '16px'
}"
>
<cc-key
*ngIf="!((item.current$ | async)?.key.data$ | async); else mapKey"

View File

@ -64,15 +64,17 @@ export class ComplexFormComponent<V, K = never>
const values = this.valueControls.value;
switch (this.data.type.name) {
case 'list':
this.emitOutgoingValue(values);
this.emitOutgoingValue(values.length ? values : null);
break;
case 'map': {
const keys = this.keyControls.value;
this.emitOutgoingValue(new Map(values.map((v, idx) => [keys[idx], v])));
this.emitOutgoingValue(
keys.length ? new Map(values.map((v, idx) => [keys[idx], v])) : null,
);
break;
}
case 'set':
this.emitOutgoingValue(new Set(values));
this.emitOutgoingValue(values.length ? new Set(values) : null);
break;
}
});

View File

@ -12,15 +12,15 @@
>
<mat-radio-group
[formControl]="control"
[required]="data.isRequired"
style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; flex: 1"
>
<mat-radio-button [value]="false">False</mat-radio-button>
<mat-radio-button [value]="true">True</mat-radio-button>
</mat-radio-group>
<button
*ngIf="
!data.isRequired && control.value !== null && control.value !== undefined
"
*ngIf="!data.isRequired"
[disabled]="control.value === null || control.value === undefined"
mat-icon-button
(click)="clear($event)"
>
@ -34,67 +34,15 @@
</ng-container>
<ng-template #input>
<div style="display: flex; gap: 4px">
<mat-form-field style="flex: 1">
<mat-label>
<ng-container *ngIf="!(extensionResult$ | async)?.label; else extensionLabel">{{
data.type | fieldLabel: data.field
}}</ng-container>
<ng-template #extensionLabel>{{
(extensionResult$ | async).label
}}</ng-template>
</mat-label>
<mat-hint>{{ aliases }}</mat-hint>
<input
#trigger="matAutocompleteTrigger"
<v-autocomplete-field
[formControl]="control"
[matAutocomplete]="auto"
[ngClass]="{ 'cc-code': (extensionResult$ | async)?.isIdentifier }"
[hint]="aliases"
[label]="(extensionResult$ | async)?.label ?? (data.type | fieldLabel: data.field)"
[options]="options$ | async"
[required]="data.isRequired"
[type]="inputType"
matInput
/>
<div matSuffix style="display: flex; gap: 4px">
<button
*ngIf="!data.isRequired && control.value"
mat-icon-button
(click)="clear($event)"
>
<mat-icon>clear</mat-icon>
</button>
<button
*ngIf="(extensionResult$ | async)?.options?.length"
mat-icon-button
(click)="
auto.isOpen ? trigger.closePanel() : trigger.openPanel();
$event.stopPropagation()
"
>
<mat-icon>{{ auto.isOpen ? 'expand_less' : 'expand_more' }}</mat-icon>
</button>
</div>
<mat-autocomplete #auto="matAutocomplete">
<ng-container *ngIf="extensionResult$ | async as extensionResult">
<mat-option
*ngFor="let option of filteredOptions$ | async"
[value]="option.value"
>
<div
style="
display: flex;
place-content: center flex-start;
align-items: center;
gap: 8px;
"
>
<div [ngClass]="{ 'cc-code': extensionResult.isIdentifier }">
{{ option.value }}
</div>
<cc-label [color]="option.color" [label]="option.label"></cc-label>
</div>
</mat-option>
</ng-container>
</mat-autocomplete>
</mat-form-field>
style="flex: 1"
></v-autocomplete-field>
<button *ngIf="generate$ | async" mat-icon-button (click)="generate($event)">
<mat-icon>loop</mat-icon>
</button>
@ -116,7 +64,9 @@
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div style="padding: 0 24px">
<cc-json-viewer [value]="selected.details"></cc-json-viewer>
</div>
</ng-template>
</mat-expansion-panel>
</ng-container>

View File

@ -1,19 +1,23 @@
import { Component, Input, OnChanges } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ComponentChanges } from '@vality/ng-core';
import {
ComponentChanges,
Option,
FormControlSuperclass,
createControlProviders,
} from '@vality/ng-core';
import { ThriftType } from '@vality/thrift-ts';
import { combineLatest, defer, ReplaySubject, switchMap, Observable } from 'rxjs';
import { map, pluck, shareReplay, startWith } from 'rxjs/operators';
import { map, shareReplay, startWith } from 'rxjs/operators';
import { getValueTypeTitle } from '@cc/app/shared';
import {
MetadataFormExtensionResult,
MetadataFormExtension,
} from '@cc/app/shared/components/metadata-form';
import { getFirstDeterminedExtensionsResult } from '@cc/app/shared/components/metadata-form/types/metadata-form-extension';
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
import { MetadataFormData, getAliases } from '../../types/metadata-form-data';
import { getFirstDeterminedExtensionsResult } from '../../types/metadata-form-extension';
@UntilDestroy()
@Component({
@ -21,10 +25,7 @@ import { MetadataFormData, getAliases } from '../../types/metadata-form-data';
templateUrl: './primitive-field.component.html',
providers: createControlProviders(() => PrimitiveFieldComponent),
})
export class PrimitiveFieldComponent<T>
extends ValidatedFormControlSuperclass<T>
implements OnChanges
{
export class PrimitiveFieldComponent<T> extends FormControlSuperclass<T> implements OnChanges {
@Input() data: MetadataFormData<ThriftType>;
@Input() extensions: MetadataFormExtension[];
@ -35,22 +36,26 @@ export class PrimitiveFieldComponent<T>
switchMap(([data, extensions]) => getFirstDeterminedExtensionsResult(extensions, data)),
shareReplay({ refCount: true, bufferSize: 1 }),
);
generate$ = this.extensionResult$.pipe(pluck('generate'));
selected$ = combineLatest([this.extensionResult$, this.control.valueChanges]).pipe(
map(([extensionResult, value]) => extensionResult?.options?.find((o) => o.value === value)),
);
filteredOptions$ = combineLatest([
this.control.valueChanges.pipe(startWith('')),
generate$ = this.extensionResult$.pipe(map((r) => r?.generate));
selected$ = combineLatest([
this.extensionResult$,
this.control.valueChanges.pipe(startWith(this.control.value)),
]).pipe(
map(([value, extensionResult]) => {
const filterValue = String(value ?? '').toLowerCase();
return extensionResult.options?.filter(
(option) =>
String(option.value).toLowerCase().includes(filterValue) ||
(option.label && option.label.toLowerCase().includes(filterValue)),
map(
([extensionResult]) =>
extensionResult?.options?.find((o) => o.value === this.control.value),
),
);
}),
options$ = this.extensionResult$.pipe(
map((extensionResult): Option<T>[] =>
extensionResult?.options?.length
? extensionResult.options.map((o) => ({
label: o.label,
value: o.value as never,
description: String(o.value),
}))
: null,
),
shareReplay({ refCount: true, bufferSize: 1 }),
);

View File

@ -1,4 +1,4 @@
<div gdColumns="1fr" gdGap="16px">
<div style="display: grid; grid-template-columns: 1fr; gap: 16px">
<ng-container *ngIf="hasLabel">
<mat-checkbox *ngIf="!labelControl.disabled; else label" [formControl]="labelControl">
<ng-container [ngTemplateOutlet]="label"></ng-container>

View File

@ -1,4 +1,4 @@
<div gdColumns="1fr" gdGap="16px">
<div style="display: grid; grid-template-columns: 1fr; gap: 16px">
<mat-form-field style="width: 100%">
<mat-label>{{ data.type | fieldLabel: data.field }}</mat-label>
<mat-select
@ -11,7 +11,8 @@
}}</mat-option>
</mat-select>
<button
*ngIf="fieldControl.value && data.isRequired"
*ngIf="!data.isRequired"
[disabled]="!fieldControl.value"
mat-icon-button
matSuffix
(click)="fieldControl.reset(); $event.stopPropagation()"

View File

@ -14,7 +14,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { DatetimeFieldModule, PipesModule } from '@vality/ng-core';
import { DatetimeFieldModule, PipesModule, AutocompleteFieldModule } from '@vality/ng-core';
import { JsonViewerModule } from '@cc/app/shared/components/json-viewer';
import { ThriftPipesModule } from '@cc/app/shared/pipes/thrift';
@ -55,6 +55,7 @@ import { FieldLabelPipe } from './pipes/field-label.pipe';
DatetimeFieldModule,
PipesModule,
CashModule,
AutocompleteFieldModule,
],
declarations: [
MetadataFormComponent,

View File

@ -1,4 +1,4 @@
<div gdColumns="1fr 1fr 1fr" gdGap="16px">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px">
<cc-details-item title="BIC">{{ internationalBankAccount.bank.bic }}</cc-details-item>
<cc-details-item title="Country">{{ internationalBankAccount.bank.country }}</cc-details-item>
<cc-details-item title="Address">{{ internationalBankAccount.bank.address }}</cc-details-item>

View File

@ -1,4 +1,4 @@
<div gdColumns="1fr 1fr 1fr" gdGap="16px">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px">
<cc-details-item title="Bank Name">{{ russianBankAccount.bank_name }}</cc-details-item>
<cc-details-item title="BIK">{{ russianBankAccount.bank_bik }}</cc-details-item>
<cc-details-item title="Bank Post Account">{{

View File

@ -1,5 +1,5 @@
<div gdGap="16px">
<div gdColumns="1fr 1fr 1fr" gdGap="16px">
<div style="display: grid; gap: 16px">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px">
<cc-details-item title="ID">{{ payoutTool.id }}</cc-details-item>
<cc-details-item title="Created At">{{ payoutTool.created_at }}</cc-details-item>
<cc-details-item title="Currency">{{ payoutTool.currency?.symbolic_code }}</cc-details-item>
@ -16,7 +16,7 @@
*ngIf="payoutTool.payout_tool_info.international_bank_account?.correspondent_account"
>
<h2 class="mat-h3">Correspondent account</h2>
<div gdColumns="1fr 1fr 1fr" gdGap="16px">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px">
<cc-international-bank-account-details
[internationalBankAccount]="
payoutTool.payout_tool_info.international_bank_account.correspondent_account

View File

@ -5,7 +5,7 @@
<mat-spinner></mat-spinner>
</div>
<ng-template #loaded>
<div class="wrapper" gdColumns="1fr" gdGap="8px" gdRows="1fr auto">
<div class="wrapper">
<cc-monaco-diff-editor
*ngIf="isDiff; else standard"
[modified]="comparedFile$ | async"

View File

@ -1,4 +1,8 @@
.wrapper {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr auto;
gap: 8px;
height: 100%;
.actions {