TD-905: Fix loading delay, fix repeat loading, default period 1 day (#354)

This commit is contained in:
Rinat Arsaev 2024-04-26 14:33:07 +09:00 committed by GitHub
parent 9145b78845
commit 7ea5d9a403
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
91 changed files with 506 additions and 998 deletions

8
package-lock.json generated
View File

@ -25,7 +25,7 @@
"@vality/fistful-proto": "2.0.1-6600be9.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-28d11b9.0",
"@vality/ng-core": "17.2.1-pr-60-8d151ad.0",
"@vality/ng-core": "17.2.1-pr-61-d842667.0",
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/scrooge-proto": "0.1.1-9ce7fc6.0",
@ -6455,9 +6455,9 @@
"integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ=="
},
"node_modules/@vality/ng-core": {
"version": "17.2.1-pr-60-8d151ad.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-60-8d151ad.0.tgz",
"integrity": "sha512-CTPRbj/W7hNBd4yYKM4u5BJK00Q93F8xrc4xJ8QusNlaJM0FGF0eqtZMAOJ4r9ViPeAbwU0c8B6gkvgwrXrvJw==",
"version": "17.2.1-pr-61-d842667.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-61-d842667.0.tgz",
"integrity": "sha512-QquwzzrFUHfc9vixSsX1ZkjQ+lkYbtEgT0tlqp1BnJ2N/kSR9eWZSV3N488j0ZPnDoJmktnwmUjDituQN855QA==",
"dependencies": {
"@angular/material-date-fns-adapter": "^17.2.0",
"@ng-matero/extensions": "^17.1.0",

View File

@ -33,7 +33,7 @@
"@vality/fistful-proto": "2.0.1-6600be9.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-28d11b9.0",
"@vality/ng-core": "17.2.1-pr-60-8d151ad.0",
"@vality/ng-core": "17.2.1-pr-61-d842667.0",
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/scrooge-proto": "0.1.1-9ce7fc6.0",

View File

@ -2,14 +2,13 @@ import { Injectable, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Domain, DomainObject, Reference } from '@vality/domain-proto/domain';
import { Commit, Snapshot, Version } from '@vality/domain-proto/domain_config';
import { NotifyLogService } from '@vality/ng-core';
import { NotifyLogService, handleError, inProgressFrom, progressTo } from '@vality/ng-core';
import isEqual from 'lodash-es/isEqual';
import { BehaviorSubject, defer, Observable, of, ReplaySubject, filter, combineLatest } from 'rxjs';
import { map, shareReplay, startWith, switchMap, take, tap } from 'rxjs/operators';
import { inProgressFrom, progressTo, getUnionKey } from '../../../../utils';
import { getUnionKey } from '../../../../utils';
import { DomainSecretService } from '../../../shared/services';
import { handleError } from '../../../shared/services/notification-error';
import { RepositoryService } from '../repository.service';
@Injectable({

View File

@ -36,8 +36,6 @@ import { DomainObjectCardComponent } from './shared/components/thrift-api-crud';
import {
DEFAULT_MAT_DATE_FORMATS,
DEFAULT_QUERY_PARAMS_SERIALIZERS,
DEFAULT_SMALL_SEARCH_LIMIT,
SMALL_SEARCH_LIMIT,
DATE_RANGE_DAYS,
DEFAULT_DATE_RANGE_DAYS,
DEBOUNCE_TIME_MS,
@ -83,7 +81,6 @@ export let AppInjector: Injector;
{ provide: MAT_DATE_FORMATS, useValue: DEFAULT_MAT_DATE_FORMATS },
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
{ provide: LOCALE_ID, useValue: 'ru' },
{ provide: SMALL_SEARCH_LIMIT, useValue: DEFAULT_SMALL_SEARCH_LIMIT },
{ provide: QUERY_PARAMS_SERIALIZERS, useValue: DEFAULT_QUERY_PARAMS_SERIALIZERS },
{ provide: DATE_RANGE_DAYS, useValue: DEFAULT_DATE_RANGE_DAYS },
{ provide: DEBOUNCE_TIME_MS, useValue: DEFAULT_DEBOUNCE_TIME_MS },

View File

@ -2,7 +2,7 @@
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
<v-filters #filters [active]="active" merge (clear)="filtersForm.reset()">
<v-filters #filters [active]="active$ | async" merge (clear)="filtersForm.reset()">
<ng-template [formGroup]="filtersForm">
<v-date-range-field formControlName="dateRange"></v-date-range-field>
<v-list-field formControlName="chargeback_ids" label="Chargeback Ids"></v-list-field>
@ -45,7 +45,7 @@
[hasMore]="hasMore$ | async"
[isLoading]="isLoading$ | async"
(more)="more()"
(update)="load($event)"
(update)="reload($event)"
>
<button
[disabled]="!selected.length"

View File

@ -1,6 +1,6 @@
import { Component, OnInit, Inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NonNullableFormBuilder } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { ChargebackSearchQuery, StatChargeback } from '@vality/magista-proto/magista';
import {
DateRange,
@ -10,14 +10,17 @@ import {
getNoTimeZoneIsoString,
createDateRangeToToday,
isEqualDateRange,
countProps,
DialogService,
DialogResponseStatus,
getValueChanges,
createControls,
debounceTimeWithFirst,
countChanged,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import merge from 'lodash-es/merge';
import { debounceTime, filter } from 'rxjs';
import { startWith } from 'rxjs/operators';
import { filter } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { Overwrite } from 'utility-types';
import {
CHARGEBACK_STATUSES,
@ -27,28 +30,37 @@ import {
import { createUnion } from '../../../utils';
import { ChangeChargebacksStatusDialogComponent } from '../../shared/components/change-chargebacks-status-dialog';
import { DATE_RANGE_DAYS } from '../../tokens';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens';
import { CreateChargebacksByFileDialogComponent } from './components/create-chargebacks-by-file-dialog/create-chargebacks-by-file-dialog.component';
import { FetchChargebacksService } from './fetch-chargebacks.service';
type FormValue = {
dateRange: DateRange;
} & Overwrite<
Omit<ChargebackSearchQuery, 'common_search_query_params'>,
Record<'chargeback_stages' | 'chargeback_categories' | 'chargeback_statuses', string[]>
> &
Pick<ChargebackSearchQuery['common_search_query_params'], 'party_id' | 'shop_ids'>;
@Component({
selector: 'cc-chargebacks',
templateUrl: './chargebacks.component.html',
styles: [],
})
export class ChargebacksComponent implements OnInit {
active = 0;
filtersForm = this.fb.group({
filtersForm = new FormGroup(
createControls<FormValue>({
dateRange: createDateRangeToToday(this.dateRangeDays),
party_id: undefined as ChargebackSearchQuery['common_search_query_params']['party_id'],
shop_ids: [undefined as ChargebackSearchQuery['common_search_query_params']['shop_ids']],
invoice_ids: [undefined as ChargebackSearchQuery['invoice_ids']],
chargeback_ids: [undefined as ChargebackSearchQuery['chargeback_ids']],
chargeback_statuses: [undefined as string[]],
chargeback_stages: [undefined as string[]],
chargeback_categories: [undefined as string[]],
});
party_id: undefined,
shop_ids: undefined,
invoice_ids: undefined,
chargeback_ids: undefined,
chargeback_statuses: undefined,
chargeback_stages: undefined,
chargeback_categories: undefined,
}),
);
chargebacks$ = this.fetchChargebacksService.result$;
isLoading$ = this.fetchChargebacksService.isLoading$;
hasMore$ = this.fetchChargebacksService.hasMore$;
@ -56,27 +68,49 @@ export class ChargebacksComponent implements OnInit {
stages = CHARGEBACK_STAGES;
categories = CHARGEBACK_CATEGORIES;
selected: StatChargeback[] = [];
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFormValue, v, { dateRange: isEqualDateRange })),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private initFormValue = this.filtersForm.value;
constructor(
private fb: NonNullableFormBuilder,
private qp: QueryParamsService<{
filters: ChargebacksComponent['filtersForm']['value'];
dateRange: DateRange;
}>,
private qp: QueryParamsService<Partial<FormValue>>,
private fetchChargebacksService: FetchChargebacksService,
private dialog: DialogService,
@Inject(DATE_RANGE_DAYS) private dateRangeDays: number,
private destroyRef: DestroyRef,
private dr: DestroyRef,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
) {}
ngOnInit() {
this.filtersForm.patchValue(
merge({}, this.qp.params.filters, clean({ dateRange: this.qp.params.dateRange })),
);
this.filtersForm.valueChanges
.pipe(startWith(null), debounceTime(500), takeUntilDestroyed(this.destroyRef))
this.filtersForm.patchValue(this.qp.params, { emitEvent: false });
getValueChanges(this.filtersForm)
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.dr))
.subscribe(() => {
this.load();
const value = clean(this.filtersForm.value);
void this.qp.set(value);
const { dateRange, party_id, shop_ids, ...rootParams } = value;
this.load({
...rootParams,
common_search_query_params: clean({
party_id,
shop_ids,
from_time: getNoTimeZoneIsoString(dateRange.start),
to_time: getNoTimeZoneIsoString(endOfDay(dateRange.end)),
}),
...clean(
{
chargeback_stages: rootParams.chargeback_stages?.map(createUnion),
chargeback_categories:
rootParams.chargeback_categories?.map(createUnion),
chargeback_statuses: rootParams.chargeback_statuses?.map(createUnion),
},
false,
true,
),
});
});
}
@ -84,34 +118,12 @@ export class ChargebacksComponent implements OnInit {
this.fetchChargebacksService.more();
}
load(options?: LoadOptions) {
const { dateRange, ...filters } = clean(this.filtersForm.value);
void this.qp.set({ filters, dateRange });
const { party_id, shop_ids, ...rootParams } = filters;
const commonParams = clean({ party_id, shop_ids });
this.fetchChargebacksService.load(
{
...rootParams,
common_search_query_params: {
...commonParams,
from_time: getNoTimeZoneIsoString(dateRange.start),
to_time: getNoTimeZoneIsoString(endOfDay(dateRange.end)),
},
...clean(
{
chargeback_stages: rootParams.chargeback_stages?.map(createUnion),
chargeback_categories: rootParams.chargeback_categories?.map(createUnion),
chargeback_statuses: rootParams.chargeback_statuses?.map(createUnion),
},
false,
true,
),
},
options,
);
this.active =
countProps(rootParams, commonParams) +
+!isEqualDateRange(createDateRangeToToday(this.dateRangeDays), dateRange);
reload(options?: LoadOptions) {
this.fetchChargebacksService.reload(options);
}
load(params: ChargebackSearchQuery, options?: LoadOptions) {
this.fetchChargebacksService.load(params, options);
}
create() {
@ -120,7 +132,7 @@ export class ChargebacksComponent implements OnInit {
.afterClosed()
.pipe(
filter((res) => res.status === DialogResponseStatus.Success),
takeUntilDestroyed(this.destroyRef),
takeUntilDestroyed(this.dr),
)
.subscribe((res) => {
this.filtersForm.reset({
@ -136,10 +148,10 @@ export class ChargebacksComponent implements OnInit {
.afterClosed()
.pipe(
filter((res) => res.status === DialogResponseStatus.Success),
takeUntilDestroyed(this.destroyRef),
takeUntilDestroyed(this.dr),
)
.subscribe(() => {
this.load();
this.reload();
});
}
}

View File

@ -1,16 +1,22 @@
import { Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { DialogResponseStatus, DialogService, NotifyLogService } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogService,
NotifyLogService,
handleError,
inProgressFrom,
progressTo,
} from '@vality/ng-core';
import { BehaviorSubject, combineLatest, defer, merge, Observable, Subject, switchMap } from 'rxjs';
import { first, map, shareReplay } from 'rxjs/operators';
import { ClaimManagementService } from '@cc/app/api/claim-management';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { getUnionKey, inProgressFrom, progressTo } from '@cc/utils';
import { getUnionKey } from '@cc/utils';
import { DomainMetadataFormExtensionsService } from '../../shared/services';
import { handleError } from '../../shared/services/notification-error';
import { AddModificationDialogComponent } from './components/add-modification-dialog/add-modification-dialog.component';
import { ChangeStatusDialogComponent } from './components/change-status-dialog/change-status-dialog.component';

View File

@ -4,12 +4,17 @@ import { Validators, FormBuilder } from '@angular/forms';
import { Claim, ModificationUnit } from '@vality/domain-proto/claim_management';
import { Party } from '@vality/domain-proto/domain';
import { ModificationChange, Modification } from '@vality/domain-proto/internal/claim_management';
import { DialogSuperclass, DEFAULT_DIALOG_CONFIG, NotifyLogService } from '@vality/ng-core';
import {
DialogSuperclass,
DEFAULT_DIALOG_CONFIG,
NotifyLogService,
inProgressFrom,
progressTo,
} from '@vality/ng-core';
import { BehaviorSubject } from 'rxjs';
import { DeepPartial } from 'utility-types';
import { ClaimManagementService } from '@cc/app/api/claim-management';
import { inProgressFrom, progressTo } from '@cc/utils';
@Component({
selector: 'cc-add-modification-dialog',

View File

@ -2,15 +2,18 @@ import { Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Validators, FormBuilder } from '@angular/forms';
import { Claim, ClaimStatus } from '@vality/domain-proto/claim_management';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogSuperclass,
NotifyLogService,
inProgressFrom,
progressTo,
} from '@vality/ng-core';
import { BehaviorSubject, Observable } from 'rxjs';
import { ClaimManagementService } from '@cc/app/api/claim-management';
import { AllowedClaimStatusesService } from '@cc/app/sections/claim/services/allowed-claim-statuses.service';
import { NotificationService } from '@cc/app/shared/services/notification';
import { getUnionKey, inProgressFrom, progressTo } from '@cc/utils';
import { NotificationErrorService } from '../../../../shared/services/notification-error';
import { getUnionKey } from '@cc/utils';
@Component({
selector: 'cc-change-status-dialog',
@ -35,9 +38,8 @@ export class ChangeStatusDialogComponent extends DialogSuperclass<
constructor(
private fb: FormBuilder,
private claimManagementService: ClaimManagementService,
private notificationService: NotificationService,
private log: NotifyLogService,
private allowedClaimStatusesService: AllowedClaimStatusesService,
private notificationErrorService: NotificationErrorService,
private destroyRef: DestroyRef,
) {
super();
@ -68,9 +70,9 @@ export class ChangeStatusDialogComponent extends DialogSuperclass<
result$.pipe(progressTo(this.progress$), takeUntilDestroyed(this.destroyRef)).subscribe({
next: () => {
this.dialogRef.close({ status: DialogResponseStatus.Success });
this.notificationService.success('Status successfully changed');
this.log.success('Status successfully changed');
},
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -20,6 +20,7 @@ import {
DialogSuperclass,
DEFAULT_DIALOG_CONFIG,
NotifyLogService,
progressTo,
} from '@vality/ng-core';
import { of, BehaviorSubject } from 'rxjs';
import { first } from 'rxjs/operators';
@ -27,7 +28,6 @@ import short from 'short-uuid';
import { MetadataFormExtension, isTypeWithAliases } from '@cc/app/shared/components/metadata-form';
import { progressTo } from '../../../../../utils';
import { ClaimManagementService } from '../../../../api/claim-management';
import { DomainStoreService } from '../../../../api/domain-config';
import { DomainThriftFormComponent } from '../../../../shared/components/thrift-api-crud';

View File

@ -8,7 +8,14 @@ import {
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Claim, ModificationUnit } from '@vality/domain-proto/claim_management';
import { DialogResponseStatus, DialogService, ConfirmDialogComponent } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogService,
ConfirmDialogComponent,
NotifyLogService,
inProgressFrom,
progressTo,
} from '@vality/ng-core';
import isEmpty from 'lodash-es/isEmpty';
import { BehaviorSubject, switchMap, from } from 'rxjs';
import { filter, first } from 'rxjs/operators';
@ -17,12 +24,9 @@ import { ClaimManagementService } from '@cc/app/api/claim-management';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { getModificationName } from '@cc/app/sections/claim/utils/get-modification-name';
import { DomainMetadataViewExtensionsService } from '@cc/app/shared/components/thrift-api-crud/domain/domain-thrift-viewer/services/domain-metadata-view-extensions';
import { NotificationService } from '@cc/app/shared/services/notification';
import { Color, StatusColor } from '@cc/app/styles';
import { inProgressFrom, progressTo } from '@cc/utils';
import { getUnionValue } from '@cc/utils/get-union-key';
import { NotificationErrorService } from '../../../../shared/services/notification-error';
import { AddModificationDialogComponent } from '../add-modification-dialog/add-modification-dialog.component';
@Component({
@ -51,9 +55,8 @@ export class ModificationUnitTimelineItemComponent {
private partyManagementService: PartyManagementService,
private dialogService: DialogService,
private claimManagementService: ClaimManagementService,
private notificationService: NotificationService,
private log: NotifyLogService,
private domainMetadataViewExtensionsService: DomainMetadataViewExtensionsService,
private notificationErrorService: NotificationErrorService,
private destroyRef: DestroyRef,
) {}
@ -108,10 +111,10 @@ export class ModificationUnitTimelineItemComponent {
)
.subscribe({
next: () => {
this.notificationService.success();
this.log.success();
this.claimChanged.emit();
},
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -2,7 +2,7 @@
<cc-page-layout-actions
><v-more-filters-button [filters]="filters"></v-more-filters-button
></cc-page-layout-actions>
<v-filters #filters [active]="active">
<v-filters #filters [active]="active$ | async">
<ng-template [formGroup]="filtersForm">
<cc-merchant-field
*ngIf="!(party$ | async)"
@ -32,7 +32,7 @@
[isLoading]="isLoading$ | async"
[noParty]="!!(party$ | async)"
(more)="more()"
(update)="load($event)"
(update)="reload($event)"
>
<button color="primary" mat-raised-button (click)="create()">Create</button>
</cc-claims-table>

View File

@ -1,4 +1,4 @@
import { Component, OnInit, DestroyRef } from '@angular/core';
import { Component, OnInit, DestroyRef, Inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NonNullableFormBuilder } from '@angular/forms';
import { PartyID } from '@vality/domain-proto/domain';
@ -8,11 +8,13 @@ import {
QueryParamsService,
clean,
getValueChanges,
countChanged,
debounceTimeWithFirst,
} from '@vality/ng-core';
import { debounceTime } from 'rxjs';
import { take } from 'rxjs/operators';
import { take, map, shareReplay } from 'rxjs/operators';
import { CLAIM_STATUSES } from '../../api/claim-management';
import { DEBOUNCE_TIME_MS } from '../../tokens';
import { PartyStoreService } from '../party';
import { CreateClaimDialogComponent } from './components/create-claim-dialog/create-claim-dialog.component';
@ -32,10 +34,14 @@ export class ClaimsComponent implements OnInit {
claim_id: undefined as number,
statuses: [[] as string[]],
});
active = 0;
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFilters, v)),
shareReplay({ refCount: true, bufferSize: 1 }),
);
party$ = this.partyStoreService.party$;
private selectedPartyId: PartyID;
private initFilters = this.filtersForm.value;
constructor(
private fetchClaimsService: FetchClaimsService,
@ -44,21 +50,21 @@ export class ClaimsComponent implements OnInit {
private qp: QueryParamsService<ClaimsComponent['filtersForm']['value']>,
private destroyRef: DestroyRef,
private partyStoreService: PartyStoreService,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
) {}
ngOnInit(): void {
this.filtersForm.patchValue(this.qp.params);
getValueChanges(this.filtersForm)
.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.load();
const filters = clean(this.filtersForm.value);
void this.qp.set(filters);
this.load(filters);
});
}
load(options?: LoadOptions): void {
const filters = clean(this.filtersForm.value);
void this.qp.set(filters);
this.active = Object.keys(filters).length;
load(filters: ClaimsComponent['filtersForm']['value'], options?: LoadOptions): void {
this.partyStoreService.party$.pipe(take(1)).subscribe((p) => {
this.fetchClaimsService.load(
clean({
@ -71,6 +77,10 @@ export class ClaimsComponent implements OnInit {
});
}
reload(options?: LoadOptions) {
this.fetchClaimsService.reload(options);
}
more(): void {
this.fetchClaimsService.more();
}

View File

@ -2,11 +2,10 @@ import { Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { DialogSuperclass, NotifyLogService, progressTo } from '@vality/ng-core';
import { BehaviorSubject } from 'rxjs';
import { ClaimManagementService } from '@cc/app/api/claim-management';
import { progressTo } from '@cc/utils';
@Component({
selector: 'cc-create-claim-dialog',

View File

@ -4,9 +4,9 @@
[columns]="columns"
[data]="reverts$ | async"
[hasMore]="hasMore$ | async"
[progress]="doAction$ | async"
(more)="fetchMore()"
(update)="update()"
[progress]="isLoading$ | async"
(more)="more()"
(update)="reload($event)"
>
<v-table-actions>
<button

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { DepositStatus, StatDeposit, StatDepositRevert } from '@vality/fistful-proto/fistful_stat';
import { DialogService, Column } from '@vality/ng-core';
import { DialogService, Column, UpdateOptions } from '@vality/ng-core';
import startCase from 'lodash-es/startCase';
import { filter } from 'rxjs/operators';
@ -21,9 +21,9 @@ import { FetchRevertsService } from './services/fetch-reverts/fetch-reverts.serv
export class RevertsComponent implements OnInit {
@Input() deposit: StatDeposit;
reverts$ = this.fetchRevertsService.searchResult$;
reverts$ = this.fetchRevertsService.result$;
hasMore$ = this.fetchRevertsService.hasMore$;
doAction$ = this.fetchRevertsService.doAction$;
isLoading$ = this.fetchRevertsService.isLoading$;
columns: Column<StatDepositRevert>[] = [
{ field: 'id' },
{
@ -53,7 +53,7 @@ export class RevertsComponent implements OnInit {
) {}
ngOnInit() {
this.fetchRevertsService.search({ deposit_id: this.deposit.id });
this.fetchRevertsService.load({ deposit_id: this.deposit.id });
}
createRevert() {
@ -65,7 +65,7 @@ export class RevertsComponent implements OnInit {
.afterClosed()
.pipe(filter((res) => res?.status === 'success'))
.subscribe(() => {
this.fetchRevertsService.refresh();
this.fetchRevertsService.reload();
});
}
@ -73,11 +73,11 @@ export class RevertsComponent implements OnInit {
return getUnionKey(status) !== 'succeeded';
}
update() {
this.fetchRevertsService.refresh();
reload(options: UpdateOptions) {
this.fetchRevertsService.reload(options);
}
fetchMore() {
this.fetchRevertsService.fetchMore();
more() {
this.fetchRevertsService.more();
}
}

View File

@ -1,37 +1,30 @@
import { Inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { StatDepositRevert } from '@vality/fistful-proto/fistful_stat';
import { Observable } from 'rxjs';
import { FetchSuperclass, FetchOptions, clean } from '@vality/ng-core';
import { map } from 'rxjs/operators';
import { createDsl, FistfulStatisticsService } from '@cc/app/api/fistful-stat';
import { FetchResult, PartialFetcher } from '@cc/app/shared/services';
import { SMALL_SEARCH_LIMIT } from '@cc/app/tokens';
import { removeEmptyProperties } from '@cc/utils';
import { FetchRevertsParams } from '../../types/fetch-reverts-params';
@Injectable()
export class FetchRevertsService extends PartialFetcher<StatDepositRevert, FetchRevertsParams> {
constructor(
private fistfulStatisticsService: FistfulStatisticsService,
@Inject(SMALL_SEARCH_LIMIT) private smallSearchLimit: number,
) {
export class FetchRevertsService extends FetchSuperclass<StatDepositRevert, FetchRevertsParams> {
constructor(private fistfulStatisticsService: FistfulStatisticsService) {
super();
}
fetch(
params: FetchRevertsParams,
continuationToken: string,
): Observable<FetchResult<StatDepositRevert>> {
fetch(params: FetchRevertsParams, options: FetchOptions) {
return this.fistfulStatisticsService
.GetDepositReverts({
dsl: createDsl({
deposit_reverts: {
...removeEmptyProperties(params),
size: this.smallSearchLimit.toString(),
...clean(params),
size: String(options.size),
},
}),
...(!!continuationToken && { continuation_token: continuationToken }),
...(!!options.continuationToken && {
continuation_token: options.continuationToken,
}),
})
.pipe(
map((res) => ({

View File

@ -2,7 +2,7 @@
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
<v-filters #filters [active]="active" merge (clear)="filtersForm.reset()">
<v-filters #filters [active]="active$ | async" merge (clear)="filtersForm.reset()">
<ng-template [formGroup]="filtersForm">
<v-date-range-field formControlName="dateRange" required></v-date-range-field>
<v-input-field formControlName="amount_to" label="Amount To"></v-input-field>
@ -28,7 +28,7 @@
[hasMore]="hasMore$ | async"
[progress]="isLoading$ | async"
(more)="more()"
(update)="update($event)"
(update)="reload($event)"
>
<v-table-actions>
<button color="primary" mat-raised-button (click)="createDeposit()">Create</button>

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit, Inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder } from '@angular/forms';
import { NonNullableFormBuilder } from '@angular/forms';
import { Router } from '@angular/router';
import { StatDeposit, RevertStatus } from '@vality/fistful-proto/fistful_stat';
import {
@ -10,15 +10,17 @@ import {
createDateRangeToToday,
QueryParamsService,
clean,
countProps,
isEqualDateRange,
getNoTimeZoneIsoString,
DialogService,
DialogResponseStatus,
debounceTimeWithFirst,
getValueChanges,
countChanged,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import startCase from 'lodash-es/startCase';
import { filter, startWith, debounceTime } from 'rxjs/operators';
import { filter, map, shareReplay } from 'rxjs/operators';
import { getUnionKey } from '../../../utils';
import { QueryDsl } from '../../api/fistful-stat';
@ -40,7 +42,7 @@ const REVERT_STATUS: { [N in RevertStatus]: string } = {
providers: [FetchDepositsService],
})
export class DepositsComponent implements OnInit {
filtersForm = this.fb.nonNullable.group({
filtersForm = this.fb.group({
dateRange: createDateRangeToToday(this.dateRangeDays),
amount_to: null as number,
currency_code: null as string,
@ -50,8 +52,6 @@ export class DepositsComponent implements OnInit {
wallet_id: '',
party_id: null as string,
});
active = 0;
deposits$ = this.fetchDepositsService.result$;
hasMore$ = this.fetchDepositsService.hasMore$;
isLoading$ = this.fetchDepositsService.isLoading$;
@ -118,12 +118,18 @@ export class DepositsComponent implements OnInit {
]),
];
depositStatuses: QueryDsl['query']['deposits']['status'][] = ['Pending', 'Succeeded', 'Failed'];
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFilters, v, { dateRange: isEqualDateRange })),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private initFilters = this.filtersForm.value;
constructor(
private dialog: DialogService,
private fetchDepositsService: FetchDepositsService,
private router: Router,
private fb: FormBuilder,
private fb: NonNullableFormBuilder,
@Inject(DATE_RANGE_DAYS) private dateRangeDays: number,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
private qp: QueryParamsService<object>,
@ -132,12 +138,8 @@ export class DepositsComponent implements OnInit {
ngOnInit() {
this.filtersForm.patchValue(this.qp.params);
this.filtersForm.valueChanges
.pipe(
startWith(this.filtersForm.value),
debounceTime(this.debounceTimeMs),
takeUntilDestroyed(this.destroyRef),
)
getValueChanges(this.filtersForm)
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.update();
});
@ -167,9 +169,10 @@ export class DepositsComponent implements OnInit {
options,
);
void this.qp.set({ dateRange, ...filters });
this.active =
countProps(filters) +
+!isEqualDateRange(dateRange, createDateRangeToToday(this.dateRangeDays));
}
reload(options?: UpdateOptions) {
this.fetchDepositsService.reload(options);
}
more() {

View File

@ -1,12 +1,14 @@
import { Injectable } from '@angular/core';
import { Injectable, Inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Party } from '@vality/domain-proto/domain';
import { progressTo } from '@vality/ng-core';
import { progressTo, debounceTimeWithFirst } from '@vality/ng-core';
import { defer, merge, Observable, Subject, BehaviorSubject } from 'rxjs';
import { map, shareReplay, switchMap, startWith, debounceTime } from 'rxjs/operators';
import { map, shareReplay, switchMap, startWith } from 'rxjs/operators';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { DEBOUNCE_TIME_MS } from '../../tokens';
@Injectable()
export class PartyShopsService {
party$: Observable<Party> = merge(
@ -14,7 +16,7 @@ export class PartyShopsService {
defer(() => this.reload$),
).pipe(
startWith(null),
debounceTime(300),
debounceTimeWithFirst(this.debounceTimeMs),
map(() => this.route.snapshot.params.partyID),
switchMap((partyID) =>
this.partyManagementService.Get(partyID).pipe(progressTo(this.progress$)),
@ -33,6 +35,7 @@ export class PartyShopsService {
constructor(
private partyManagementService: PartyManagementService,
private route: ActivatedRoute,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
) {}
reload() {

View File

@ -3,14 +3,12 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl } from '@angular/forms';
import { InvoicePaymentChargeback } from '@vality/domain-proto/domain';
import { InvoicePaymentChargebackParams } from '@vality/domain-proto/payment_processing';
import { DialogSuperclass } from '@vality/ng-core';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { from } from 'rxjs';
import * as short from 'short-uuid';
import { InvoicingService } from '@cc/app/api/payment-processing';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
import { NotificationService } from '@cc/app/shared/services/notification';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
@Component({
selector: 'cc-create-chargeback-dialog',
@ -28,8 +26,7 @@ export class CreateChargebackDialogComponent extends DialogSuperclass<
constructor(
private invoicingService: InvoicingService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
private notificationErrorService: NotificationErrorService,
private notificationService: NotificationService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -45,10 +42,10 @@ export class CreateChargebackDialogComponent extends DialogSuperclass<
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (res) => {
this.notificationService.success('Chargeback created');
this.log.success('Chargeback created');
this.closeWithSuccess(res);
},
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -1,6 +1,8 @@
<v-table
[columns]="columns"
[data]="refunds$ | async"
[hasMore]="hasMore$ | async"
[progress]="isLoading$ | async"
(more)="fetchMore()"
(more)="more()"
(update)="reload($event)"
></v-table>

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { InvoicePaymentID, InvoiceID, PartyID } from '@vality/domain-proto/domain';
import { Column, NotifyLogService } from '@vality/ng-core';
import { Column, UpdateOptions } from '@vality/ng-core';
import startCase from 'lodash-es/startCase';
import { getUnionKey } from '../../../../utils';
@ -21,9 +21,9 @@ export class RefundsTableComponent implements OnInit {
@Input() invoiceID: InvoiceID;
@Input() partyID: PartyID;
isLoading$ = this.fetchRefundsService.doAction$;
isLoading$ = this.fetchRefundsService.isLoading$;
hasMore$ = this.fetchRefundsService.hasMore$;
refunds$ = this.fetchRefundsService.searchResult$;
refunds$ = this.fetchRefundsService.result$;
columns: Column<Refund>[] = [
{ field: 'created_at', type: 'datetime' },
@ -48,23 +48,21 @@ export class RefundsTableComponent implements OnInit {
'reason',
];
constructor(
private fetchRefundsService: FetchRefundsService,
private log: NotifyLogService,
) {}
constructor(private fetchRefundsService: FetchRefundsService) {}
ngOnInit() {
this.fetchRefundsService.search({
this.fetchRefundsService.load({
common_search_query_params: { party_id: this.partyID },
payment_id: this.paymentID,
invoice_ids: [this.invoiceID],
});
this.fetchRefundsService.errors$.subscribe((e) =>
this.log.errorOperation(e, 'receive', 'refunds'),
);
}
fetchMore() {
this.fetchRefundsService.fetchMore();
more() {
this.fetchRefundsService.more();
}
reload(options: UpdateOptions) {
this.fetchRefundsService.reload(options);
}
}

View File

@ -3,12 +3,7 @@ import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { TableModule } from '@vality/ng-core';
import {
StatusModule,
CommonPipesModule,
ThriftPipesModule,
AmountCurrencyPipe,
} from '../../../shared';
import { StatusModule, CommonPipesModule, ThriftPipesModule } from '../../../shared';
import { RefundsTableComponent } from './refunds-table.component';
@ -19,7 +14,6 @@ import { RefundsTableComponent } from './refunds-table.component';
StatusModule,
ThriftPipesModule,
CommonPipesModule,
AmountCurrencyPipe,
TableModule,
],
declarations: [RefundsTableComponent],

View File

@ -1,36 +1,38 @@
import { Injectable } from '@angular/core';
import { StatRefund, RefundSearchQuery } from '@vality/magista-proto/magista';
import { cleanPrimitiveProps } from '@vality/ng-core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import {
cleanPrimitiveProps,
FetchSuperclass,
FetchOptions,
NotifyLogService,
} from '@vality/ng-core';
import { of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { DeepPartial } from 'utility-types';
import { MerchantStatisticsService } from '../../../../api/magista';
import { FetchResult, PartialFetcher } from '../../../../shared/services';
const SEARCH_LIMIT = 5;
@Injectable()
export class FetchRefundsService extends PartialFetcher<
export class FetchRefundsService extends FetchSuperclass<
StatRefund,
DeepPartial<RefundSearchQuery>
> {
constructor(private merchantStatisticsService: MerchantStatisticsService) {
constructor(
private merchantStatisticsService: MerchantStatisticsService,
private log: NotifyLogService,
) {
super();
}
protected fetch(
params: DeepPartial<RefundSearchQuery>,
continuationToken: string,
): Observable<FetchResult<StatRefund>> {
protected fetch(params: DeepPartial<RefundSearchQuery>, options: FetchOptions) {
return this.merchantStatisticsService
.SearchRefunds(
cleanPrimitiveProps({
...params,
common_search_query_params: Object.assign(
{
continuation_token: continuationToken,
limit: SEARCH_LIMIT,
continuation_token: options.continuationToken,
limit: options.size,
from_time: new Date(0).toISOString(), // TODO
to_time: new Date().toISOString(),
},
@ -43,6 +45,10 @@ export class FetchRefundsService extends PartialFetcher<
result: refunds,
continuationToken: continuation_token,
})),
catchError((err) => {
this.log.errorOperation(err, 'receive', 'refunds');
return of({ result: [] });
}),
);
}
}

View File

@ -4,7 +4,7 @@
</cc-page-layout-actions>
<v-filters
#filters
[active]="active"
[active]="active$ | async"
merge
(clear)="filtersForm.reset(); otherFiltersControl.reset()"
>
@ -29,13 +29,12 @@
<v-input-field formControlName="error_message" label="Error message"></v-input-field>
</ng-template>
<ng-template vOtherFilters>
<cc-metadata-form
<cc-magista-thrift-form
[extensions]="extensions"
[formControl]="otherFiltersControl"
[metadata]="metadata$ | async"
namespace="magista"
noToolbar
type="PaymentSearchQuery"
></cc-metadata-form>
></cc-magista-thrift-form>
</ng-template>
</v-filters>
<cc-payments-table
@ -45,7 +44,7 @@
[selected]="selected$ | async"
(more)="more()"
(selectedChange)="selected$.next($event)"
(update)="update($event ?? {})"
(update)="reload($event ?? {})"
>
<button
[disabled]="!(selected$ | async)?.length"

View File

@ -1,7 +1,6 @@
import { Component, OnInit, Inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NonNullableFormBuilder } from '@angular/forms';
import { ThriftAstMetadata } from '@vality/fistful-proto';
import { StatPayment } from '@vality/magista-proto/magista';
import {
DialogService,
@ -12,20 +11,20 @@ import {
DateRange,
QueryParamsService,
createDateRangeToToday,
countProps,
isEqualDateRange,
debounceTimeWithFirst,
getValueChanges,
countChanged,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import { uniq } from 'lodash-es';
import isEqual from 'lodash-es/isEqual';
import lodashMerge from 'lodash-es/merge';
import omit from 'lodash-es/omit';
import { BehaviorSubject, debounceTime, from, of, merge } from 'rxjs';
import { startWith, map, distinctUntilChanged } from 'rxjs/operators';
import { BehaviorSubject, of, merge } from 'rxjs';
import { startWith, map, distinctUntilChanged, shareReplay } from 'rxjs/operators';
import { FailMachinesDialogComponent, Type } from '../../shared/components/fail-machines-dialog';
import { MetadataFormExtension, isTypeWithAliases } from '../../shared/components/metadata-form';
import { DATE_RANGE_DAYS } from '../../tokens';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens';
import { CreatePaymentAdjustmentComponent } from './components/create-payment-adjustment/create-payment-adjustment.component';
import { FetchPaymentsService } from './services/fetch-payments.service';
@ -60,9 +59,6 @@ export class PaymentsComponent implements OnInit {
common_search_query_params: {},
payment_params: {},
});
metadata$ = from(
import('@vality/magista-proto/metadata.json').then((m) => m.default as ThriftAstMetadata[]),
);
extensions: MetadataFormExtension[] = [
{
determinant: (data) =>
@ -81,7 +77,14 @@ export class PaymentsComponent implements OnInit {
extension: () => of({ hidden: true }),
},
];
active = 0;
active$ = getValueChanges(this.filtersForm).pipe(
map((filters) =>
countChanged(this.initFiltersValue, filters, { dateRange: isEqualDateRange }),
),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private initFiltersValue = this.filtersForm.value;
constructor(
private qp: QueryParamsService<Filters>,
@ -89,29 +92,33 @@ export class PaymentsComponent implements OnInit {
private dialogService: DialogService,
private fb: NonNullableFormBuilder,
@Inject(DATE_RANGE_DAYS) private dateRangeDays: number,
private destroyRef: DestroyRef,
private dr: DestroyRef,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
) {}
ngOnInit() {
this.filtersForm.patchValue(
lodashMerge({}, this.qp.params.filters, clean({ dateRange: this.qp.params.dateRange })),
);
this.otherFiltersControl.patchValue(
lodashMerge({}, this.otherFiltersControl.value, this.qp.params.otherFilters || {}),
Object.assign(
{},
this.qp.params.filters,
clean({ dateRange: this.qp.params.dateRange }),
),
);
this.otherFiltersControl.patchValue(Object.assign({}, this.qp.params.otherFilters));
merge(this.filtersForm.valueChanges, this.otherFiltersControl.valueChanges)
.pipe(
startWith(null),
debounceTime(500),
debounceTimeWithFirst(this.debounceTimeMs),
map(() => {
const { dateRange, ...filters } = clean(this.filtersForm.value);
const otherFilters = clean(this.otherFiltersControl.value);
return { filters, dateRange, otherFilters };
}),
distinctUntilChanged(isEqual),
takeUntilDestroyed(this.destroyRef),
takeUntilDestroyed(this.dr),
)
.subscribe((filters) => {
void this.qp.set(filters);
this.load(filters);
});
}
@ -121,7 +128,6 @@ export class PaymentsComponent implements OnInit {
}
load({ filters, otherFilters, dateRange }: Filters, options?: LoadOptions) {
void this.qp.set({ filters, otherFilters, dateRange });
const { invoice_ids, party_id, shop_ids, external_id, ...paymentParams } = filters;
const searchParams = clean({
...otherFilters,
@ -137,15 +143,9 @@ export class PaymentsComponent implements OnInit {
invoice_ids,
});
this.fetchPaymentsService.load(searchParams, options);
this.active =
countProps(
omit(searchParams, 'payment_params', 'common_search_query_params'),
searchParams.payment_params,
omit(searchParams.common_search_query_params, 'from_time', 'to_time'),
) + +!isEqualDateRange(dateRange, createDateRangeToToday(this.dateRangeDays));
}
update(options?: LoadOptions) {
reload(options?: LoadOptions) {
this.fetchPaymentsService.reload(options);
}
@ -157,7 +157,7 @@ export class PaymentsComponent implements OnInit {
.afterClosed()
.subscribe((res) => {
if (res.status === DialogResponseStatus.Success) {
this.update();
this.reload();
this.selected$.next([]);
} else if (res.data?.errors?.length) {
this.selected$.next(res.data.errors.map(({ data }) => data));
@ -174,7 +174,7 @@ export class PaymentsComponent implements OnInit {
.afterClosed()
.subscribe((res) => {
if (res.status === DialogResponseStatus.Success) {
this.update();
this.reload();
this.selected$.next([]);
} else if (res.data?.errors?.length) {
this.selected$.next(

View File

@ -15,6 +15,8 @@ import { PageLayoutModule, ShopFieldModule } from '@cc/app/shared';
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { MagistaThriftFormComponent } from '../../shared/components/thrift-api-crud';
import { CreatePaymentAdjustmentComponent } from './components/create-payment-adjustment/create-payment-adjustment.component';
import { PaymentsTableComponent } from './components/payments-table/payments-table.component';
import { PaymentsRoutingModule } from './payments-routing.module';
@ -37,6 +39,7 @@ import { PaymentsComponent } from './payments.component';
ShopFieldModule,
InputFieldModule,
FormsModule,
MagistaThriftFormComponent,
],
declarations: [PaymentsComponent, CreatePaymentAdjustmentComponent, PaymentsTableComponent],
})

View File

@ -1,15 +1,16 @@
import { Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl } from '@angular/forms';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogSuperclass,
NotifyLogService,
progressTo,
} from '@vality/ng-core';
import { PayoutID } from '@vality/payout-manager-proto/payout_manager';
import { BehaviorSubject } from 'rxjs';
import { PayoutManagementService } from '@cc/app/api/payout-manager';
import { NotificationService } from '@cc/app/shared/services/notification';
import { progressTo } from '@cc/utils/operators';
import { NotificationErrorService } from '../../../../../shared/services/notification-error';
@Component({
selector: 'cc-cancel-payout-dialog',
@ -24,8 +25,7 @@ export class CancelPayoutDialogComponent extends DialogSuperclass<
constructor(
private payoutManagementService: PayoutManagementService,
private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -38,9 +38,9 @@ export class CancelPayoutDialogComponent extends DialogSuperclass<
.subscribe({
next: () => {
this.dialogRef.close({ status: DialogResponseStatus.Success });
this.notificationService.success('Payout canceled successfully');
this.log.success('Payout canceled successfully');
},
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -1,18 +1,19 @@
import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder } from '@angular/forms';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogSuperclass,
NotifyLogService,
progressTo,
toMinor,
} from '@vality/ng-core';
import { PayoutParams } from '@vality/payout-manager-proto/payout_manager';
import isNil from 'lodash-es/isNil';
import omitBy from 'lodash-es/omitBy';
import { BehaviorSubject } from 'rxjs';
import { PayoutManagementService } from '@cc/app/api/payout-manager';
import { NotificationService } from '@cc/app/shared/services/notification';
import { progressTo } from '@cc/utils/operators';
import { toMinor } from '@cc/utils/to-minor';
import { NotificationErrorService } from '../../../../../shared/services/notification-error';
interface CreatePayoutDialogForm {
partyId: string;
@ -40,8 +41,7 @@ export class CreatePayoutDialogComponent extends DialogSuperclass<CreatePayoutDi
constructor(
private fb: FormBuilder,
private payoutManagementService: PayoutManagementService,
private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -58,7 +58,7 @@ export class CreatePayoutDialogComponent extends DialogSuperclass<CreatePayoutDi
party_id: value.partyId,
},
cash: {
amount: toMinor(value.amount),
amount: toMinor(value.amount, value.currency), // TODO use domain currencies refs
currency: { symbolic_code: value.currency },
},
payout_tool_id: value.payoutToolId,
@ -69,10 +69,10 @@ export class CreatePayoutDialogComponent extends DialogSuperclass<CreatePayoutDi
.pipe(takeUntilDestroyed(this.destroyRef), progressTo(this.progress$))
.subscribe({
next: () => {
this.notificationService.success('Payout created successfully');
this.log.success('Payout created successfully');
this.dialogRef.close({ status: DialogResponseStatus.Success });
},
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -2,7 +2,7 @@
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
<v-filters #filters [active]="active" merge (clear)="filtersForm.reset()">
<v-filters #filters [active]="active$ | async" merge (clear)="filtersForm.reset()">
<ng-template [formGroup]="filtersForm">
<v-date-range-field formControlName="dateRange" required></v-date-range-field>
<mat-form-field>
@ -52,7 +52,7 @@
[hasMore]="hasMore$ | async"
[progress]="inProgress$ | async"
(more)="more()"
(update)="search($event)"
(update)="reload($event)"
>
<v-table-actions>
<button color="primary" mat-raised-button (click)="create()">Create</button>

View File

@ -1,6 +1,6 @@
import { Component, OnInit, Inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder } from '@angular/forms';
import { NonNullableFormBuilder } from '@angular/forms';
import { Party, Shop, ShopID, PartyID } from '@vality/domain-proto/domain';
import { magista } from '@vality/magista-proto';
import { StatPayout } from '@vality/magista-proto/magista';
@ -14,18 +14,17 @@ import {
Column,
createOperationColumn,
DateRange,
countProps,
isEqualDateRange,
UpdateOptions,
debounceTimeWithFirst,
countChanged,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import omit from 'lodash-es/omit';
import startCase from 'lodash-es/startCase';
import { filter } from 'rxjs/operators';
import { map, shareReplay } from 'rxjs/operators';
import { getUnionKey } from '../../../../utils';
import { createCurrencyColumn, createPartyColumn, createShopColumn } from '../../../shared';
import { DATE_RANGE_DAYS } from '../../../tokens';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../../tokens';
import { PayoutActionsService } from '../services/payout-actions.service';
import { CreatePayoutDialogComponent } from './components/create-payout-dialog/create-payout-dialog.component';
@ -102,7 +101,12 @@ export class PayoutsComponent implements OnInit {
];
statusTypeEnum = magista.PayoutStatusType;
payoutToolTypeEnum = magista.PayoutToolType;
active = 0;
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFilters, v)),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private initFilters = this.filtersForm.value;
constructor(
private fetchPayoutsService: FetchPayoutsService,
@ -110,19 +114,19 @@ export class PayoutsComponent implements OnInit {
private dialogService: DialogService,
@Inject(DATE_RANGE_DAYS) private dateRangeDays: number,
private payoutActionsService: PayoutActionsService,
private fb: FormBuilder,
private fb: NonNullableFormBuilder,
private destroyRef: DestroyRef,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
) {}
ngOnInit() {
this.filtersForm.patchValue(this.qp.params);
getValueChanges(this.filtersForm)
.pipe(
filter(() => this.filtersForm.valid),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((value) => void this.qp.set(clean(value)));
this.qp.params$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.search());
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.qp.set(clean(value));
this.search();
});
}
more() {
@ -130,10 +134,7 @@ export class PayoutsComponent implements OnInit {
}
search(options?: UpdateOptions) {
const value = this.qp.params;
if (!value.dateRange) {
return;
}
const value = clean(this.filtersForm.value);
this.fetchPayoutsService.load(
clean({
common_search_query_params: clean({
@ -151,9 +152,10 @@ export class PayoutsComponent implements OnInit {
}),
options,
);
this.active =
countProps(omit(value, 'dateRange')) +
+!isEqualDateRange(value.dateRange, createDateRangeToToday(this.dateRangeDays));
}
reload(options?: UpdateOptions) {
this.fetchPayoutsService.reload(options);
}
create() {

View File

@ -1,12 +1,16 @@
import { Injectable, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PayoutID, PayoutStatus } from '@vality/magista-proto/magista';
import { DialogResponseStatus, DialogService, ConfirmDialogComponent } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogService,
ConfirmDialogComponent,
NotifyLogService,
} from '@vality/ng-core';
import { switchMap } from 'rxjs';
import { filter } from 'rxjs/operators';
import { PayoutManagementService } from '@cc/app/api/payout-manager';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import { CancelPayoutDialogComponent } from '../payouts/components/cancel-payout-dialog/cancel-payout-dialog.component';
@ -15,7 +19,7 @@ export class PayoutActionsService {
constructor(
private payoutManagementService: PayoutManagementService,
private dialogService: DialogService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {}
@ -41,7 +45,7 @@ export class PayoutActionsService {
takeUntilDestroyed(this.destroyRef),
)
.subscribe({
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -1,7 +1,13 @@
import { Component, OnInit, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Validators, FormControl } from '@angular/forms';
import { DialogResponseStatus, DialogSuperclass, getValue } from '@vality/ng-core';
import {
DialogResponseStatus,
DialogSuperclass,
getValue,
NotifyLogService,
progressTo,
} from '@vality/ng-core';
import {
RepairInvoicesRequest,
RepairWithdrawalsRequest,
@ -12,11 +18,8 @@ import { BehaviorSubject, from } from 'rxjs';
import { map } from 'rxjs/operators';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import { progressTo } from '../../../../../utils';
import { RepairManagementService } from '../../../../api/repairer';
import { NotificationService } from '../../../../shared/services/notification';
enum Types {
Same,
@ -58,8 +61,7 @@ export class RepairByScenarioDialogComponent
constructor(
private repairManagementService: RepairManagementService,
private notificationErrorService: NotificationErrorService,
private notificationService: NotificationService,
private log: NotifyLogService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
private destroyRef: DestroyRef,
) {
@ -91,10 +93,10 @@ export class RepairByScenarioDialogComponent
.pipe(progressTo(this.progress$), takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
this.notificationService.success();
this.log.success();
this.dialogRef.close({ status: DialogResponseStatus.Success });
},
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -2,7 +2,7 @@
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
<v-filters #filters [active]="active" merge (clear)="filtersForm.reset()">
<v-filters #filters [active]="active$ | async" merge (clear)="filtersForm.reset()">
<ng-template [formGroup]="filtersForm">
<v-date-range-field formControlName="timespan"></v-date-range-field>
<v-list-field formControlName="ids" label="IDs"></v-list-field>
@ -40,9 +40,9 @@
[progress]="inProgress$ | async"
[rowSelected]="selected$ | async"
rowSelectable
(more)="fetchMore()"
(more)="more()"
(rowSelectedChange)="selected$.next($event)"
(update)="update($event.size)"
(update)="reload($event)"
>
<v-table-actions>
<button

View File

@ -11,25 +11,25 @@ import {
NotifyLogService,
DateRange,
getNoTimeZoneIsoString,
countProps,
createDateRangeToToday,
isEqualDateRange,
getValueChanges,
countChanged,
debounceTimeWithFirst,
FetchOptions,
getEnumKey,
} from '@vality/ng-core';
import { repairer } from '@vality/repairer-proto';
import { Namespace, ProviderID, RepairStatus, Machine } from '@vality/repairer-proto/repairer';
import { endOfDay } from 'date-fns';
import isNil from 'lodash-es/isNil';
import omit from 'lodash-es/omit';
import startCase from 'lodash-es/startCase';
import { BehaviorSubject } from 'rxjs';
import { filter, switchMap, debounceTime } from 'rxjs/operators';
import { getEnumKey } from '@cc/utils';
import { filter, switchMap, map, shareReplay } from 'rxjs/operators';
import { RepairManagementService } from '../../api/repairer';
import { createProviderColumn } from '../../shared/utils/table/create-provider-column';
import { DATE_RANGE_DAYS } from '../../tokens';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens';
import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component';
import { MachinesService } from './services/machines.service';
@ -49,8 +49,8 @@ interface Filters {
providers: [MachinesService],
})
export class RepairingComponent implements OnInit {
machines$ = this.machinesService.searchResult$;
inProgress$ = this.machinesService.doAction$;
machines$ = this.machinesService.result$;
inProgress$ = this.machinesService.isLoading$;
hasMore$ = this.machinesService.hasMore$;
filtersForm = this.fb.group({
ids: [null as string[]],
@ -89,7 +89,12 @@ export class RepairingComponent implements OnInit {
field: 'error_message',
},
];
active = 0;
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFilters, v, { timespan: isEqualDateRange })),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private initFilters = this.filtersForm.value;
constructor(
private machinesService: MachinesService,
@ -100,16 +105,17 @@ export class RepairingComponent implements OnInit {
private log: NotifyLogService,
private destroyRef: DestroyRef,
@Inject(DATE_RANGE_DAYS) private dateRangeDays: number,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
) {}
ngOnInit() {
this.filtersForm.patchValue(this.qp.params);
getValueChanges(this.filtersForm)
.pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef))
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.subscribe((params: Filters) => {
const { ids, ns, timespan, provider_id, status, error_message } = params;
void this.qp.set(clean(params));
this.machinesService.search(
this.machinesService.load(
clean({
ids,
ns,
@ -125,19 +131,15 @@ export class RepairingComponent implements OnInit {
: null,
}),
);
this.active =
countProps(omit(clean(params), 'timespan')) +
+!isEqualDateRange(timespan, createDateRangeToToday(this.dateRangeDays));
});
}
update(size: number) {
this.machinesService.refresh(size);
this.selected$.next([]);
reload(options?: FetchOptions) {
this.machinesService.reload(options);
}
fetchMore() {
this.machinesService.fetchMore();
more() {
this.machinesService.more();
}
repair() {

View File

@ -1,32 +1,36 @@
import { Injectable } from '@angular/core';
import { FetchSuperclass, NotifyLogService, FetchOptions } from '@vality/ng-core';
import { Machine, SearchRequest } from '@vality/repairer-proto/repairer';
import { map } from 'rxjs/operators';
import { of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { RepairManagementService } from '../../../api/repairer';
import { PartialFetcher } from '../../../shared/services';
import { NotificationErrorService } from '../../../shared/services/notification-error';
@Injectable()
export class MachinesService extends PartialFetcher<Machine, SearchRequest> {
export class MachinesService extends FetchSuperclass<Machine, SearchRequest> {
constructor(
private repairManagementService: RepairManagementService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
) {
super();
}
protected fetch(params: SearchRequest, continuationToken: string, size: number) {
protected fetch(params: SearchRequest, options: FetchOptions) {
return this.repairManagementService
.Search({ limit: size, continuation_token: continuationToken, ...params })
.Search({
limit: options.size,
continuation_token: options.continuationToken,
...params,
})
.pipe(
map(({ machines, continuation_token }) => ({
result: machines,
continuationToken: continuation_token,
})),
catchError((err) => {
this.log.error(err);
return of({ result: [] });
}),
);
}
protected handleError(err) {
this.notificationErrorService.error(err);
}
}

View File

@ -1,10 +1,8 @@
import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DialogSuperclass } from '@vality/ng-core';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { BehaviorSubject } from 'rxjs';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import { RoutingRulesService } from '../services/routing-rules';
import { TargetRuleset } from '../target-ruleset-form';
import { RoutingRulesType } from '../types/routing-rules-type';
@ -23,7 +21,7 @@ export class ChangeTargetDialogComponent extends DialogSuperclass<
constructor(
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -51,6 +49,6 @@ export class ChangeTargetDialogComponent extends DialogSuperclass<
delegateIdx,
})
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.dialogRef.close(), this.notificationErrorService.error);
.subscribe(() => this.dialogRef.close(), this.log.error);
}
}

View File

@ -1,11 +1,10 @@
import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UntypedFormBuilder } from '@angular/forms';
import { DialogSuperclass } from '@vality/ng-core';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { BehaviorSubject } from 'rxjs';
import { RoutingRulesType } from '@cc/app/sections/routing-rules/types/routing-rules-type';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import { RoutingRulesService } from '../../services/routing-rules';
import { TargetRuleset } from '../../target-ruleset-form';
@ -31,7 +30,7 @@ export class AttachNewRulesetDialogComponent extends DialogSuperclass<
constructor(
private fb: UntypedFormBuilder,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -49,7 +48,7 @@ export class AttachNewRulesetDialogComponent extends DialogSuperclass<
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => this.dialogRef.close(),
error: (err) => this.notificationErrorService.error(err),
error: (err) => this.log.error(err),
});
}
}

View File

@ -2,12 +2,11 @@ import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
import { DialogService, NotifyLogService } from '@vality/ng-core';
import { first, map } from 'rxjs/operators';
import { first, map, catchError } from 'rxjs/operators';
import { DomainStoreService } from '@cc/app/api/domain-config';
import { RoutingRulesType } from '@cc/app/sections/routing-rules/types/routing-rules-type';
import { handleError } from '../../../../utils';
import { RoutingRulesTypeService } from '../routing-rules-type.service';
import { RoutingRulesService } from '../services/routing-rules';
@ -73,7 +72,13 @@ export class PartyDelegateRulesetsComponent {
type: this.route.snapshot.params.type,
})
.afterClosed()
.pipe(handleError(this.log.error), takeUntilDestroyed(this.destroyRef))
.pipe(
catchError((err) => {
this.log.error(err);
throw err;
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe();
}

View File

@ -3,9 +3,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder } from '@angular/forms';
import { Shop } from '@vality/domain-proto/domain';
import { StatWallet } from '@vality/fistful-proto/fistful_stat';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import { DialogResponseStatus, DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { RoutingRulesService } from '../../services/routing-rules';
import { RoutingRulesType } from '../../types/routing-rules-type';
@ -27,7 +25,7 @@ export class AddPartyRoutingRuleDialogComponent extends DialogSuperclass<
constructor(
private fb: FormBuilder,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -49,7 +47,7 @@ export class AddPartyRoutingRuleDialogComponent extends DialogSuperclass<
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => this.dialogRef.close({ status: DialogResponseStatus.Success }),
error: this.notificationErrorService.error,
error: this.log.error,
});
}
}

View File

@ -1,9 +1,8 @@
import { Component, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UntypedFormBuilder } from '@angular/forms';
import { DialogSuperclass } from '@vality/ng-core';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { NotificationErrorService } from '../../../../shared/services/notification-error';
import { RoutingRulesService } from '../../services/routing-rules';
@Component({
@ -23,7 +22,7 @@ export class InitializeRoutingRulesDialogComponent extends DialogSuperclass<
constructor(
private fb: UntypedFormBuilder,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
private destroyRef: DestroyRef,
) {
super();
@ -40,6 +39,6 @@ export class InitializeRoutingRulesDialogComponent extends DialogSuperclass<
delegateDescription,
})
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.dialogRef.close(), this.notificationErrorService.error);
.subscribe(() => this.dialogRef.close(), this.log.error);
}
}

View File

@ -19,9 +19,8 @@ import {
ComponentChanges,
NotifyLogService,
} from '@vality/ng-core';
import { filter, switchMap } from 'rxjs/operators';
import { filter, switchMap, catchError } from 'rxjs/operators';
import { handleError } from '../../../../utils/operators/handle-error';
import { ChangeDelegateRulesetDialogComponent } from '../change-delegate-ruleset-dialog';
import { ChangeTargetDialogComponent } from '../change-target-dialog';
import { RoutingRulesService } from '../services/routing-rules';
@ -120,7 +119,13 @@ export class RoutingRulesListComponent<
delegateIdx: delegateId.delegateIdx,
})
.afterClosed()
.pipe(handleError(this.log.error), takeUntilDestroyed(this.destroyRef))
.pipe(
catchError((err) => {
this.log.error(err);
throw err;
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe();
}

View File

@ -1,10 +1,9 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { getEnumValues } from '@vality/ng-core';
import { Observable } from 'rxjs';
import { startWith, map } from 'rxjs/operators';
import { getEnumValues } from '../../../utils';
import { RoutingRulesType } from './types/routing-rules-type';
@Injectable()

View File

@ -7,6 +7,6 @@
externalFilter
noActions
(filterChange)="searchParamsUpdated($event)"
(update)="update()"
(update)="reload($event)"
></v-table>
</cc-page-layout>

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Party } from '@vality/deanonimus-proto/deanonimus';
import { Column, createOperationColumn, QueryParamsService } from '@vality/ng-core';
import { Column, createOperationColumn, QueryParamsService, UpdateOptions } from '@vality/ng-core';
import startCase from 'lodash-es/startCase';
import { map } from 'rxjs/operators';
@ -16,8 +16,8 @@ import { getUnionKey } from '../../../utils';
})
export class SearchPartiesComponent {
initSearchParams$ = this.qp.params$.pipe(map((p) => p?.text ?? ''));
inProgress$ = this.fetchPartiesService.inProgress$;
parties$ = this.fetchPartiesService.parties$;
inProgress$ = this.fetchPartiesService.isLoading$;
parties$ = this.fetchPartiesService.result$;
columns: Column<Party>[] = [
{ field: 'id' },
{
@ -70,10 +70,10 @@ export class SearchPartiesComponent {
searchParamsUpdated(filter: string) {
void this.qp.set({ text: filter });
this.fetchPartiesService.searchParties(filter);
this.fetchPartiesService.load(filter);
}
update() {
this.fetchPartiesService.searchParties(this.qp.params.text);
reload(options: UpdateOptions) {
this.fetchPartiesService.reload(options);
}
}

View File

@ -1,10 +1,8 @@
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { DialogSuperclass } from '@vality/ng-core';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import { FistfulAdminService } from '../../../api/fistful-admin';
import { NotificationService } from '../../../shared/services/notification';
import { NotificationErrorService } from '../../../shared/services/notification-error';
@Component({
selector: 'cc-create-source',
@ -15,8 +13,7 @@ export class CreateSourceComponent extends DialogSuperclass<void> {
constructor(
private fistfulAdminService: FistfulAdminService,
private errorService: NotificationErrorService,
private notificationService: NotificationService,
private log: NotifyLogService,
) {
super();
}
@ -25,10 +22,10 @@ export class CreateSourceComponent extends DialogSuperclass<void> {
this.fistfulAdminService.CreateSource(this.control.value).subscribe({
next: () => {
this.closeWithSuccess();
this.notificationService.success('Source created successfully!');
this.log.success('Source created successfully!');
},
error: (err) => {
this.errorService.error(err);
this.log.error(err);
},
});
}

View File

@ -1,11 +1,10 @@
import { Injectable } from '@angular/core';
import { StatSource } from '@vality/fistful-proto/internal/fistful_stat';
import { compareDifferentTypes, NotifyLogService } from '@vality/ng-core';
import { compareDifferentTypes, NotifyLogService, progressTo } from '@vality/ng-core';
import { Observable, switchMap, of, BehaviorSubject } from 'rxjs';
import { shareReplay, map, catchError } from 'rxjs/operators';
import { FistfulStatisticsService, createDsl } from '@cc/app/api/fistful-stat';
import { progressTo } from '@cc/utils';
@Injectable({
providedIn: 'root',

View File

@ -10,7 +10,12 @@
<!-- ]"-->
<!-- ></v-switch-button>-->
</cc-page-layout-actions>
<v-filters *ngIf="isFilterControl.value" [active]="active" merge (clear)="filtersForm.reset()">
<v-filters
*ngIf="isFilterControl.value"
[active]="active$ | async"
merge
(clear)="filtersForm.reset()"
>
<ng-template [formGroup]="filtersForm">
<cc-merchant-field
*ngIf="!(party$ | async)"

View File

@ -8,7 +8,7 @@ import {
runInInjectionContext,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, FormControl } from '@angular/forms';
import { FormControl, NonNullableFormBuilder } from '@angular/forms';
import { SearchWalletHit } from '@vality/deanonimus-proto/internal/deanonimus';
import { IdentityState } from '@vality/fistful-proto/identity';
import { AccountBalance } from '@vality/fistful-proto/internal/account';
@ -18,14 +18,15 @@ import {
Column,
QueryParamsService,
NotifyLogService,
countProps,
FiltersComponent,
UpdateOptions,
getValueChanges,
countChanged,
debounceTimeWithFirst,
} from '@vality/ng-core';
import isNil from 'lodash-es/isNil';
import { of } from 'rxjs';
import { map, shareReplay, catchError, debounceTime, take } from 'rxjs/operators';
import { map, shareReplay, catchError, take } from 'rxjs/operators';
import { MemoizeExpiring } from 'typescript-memoize';
import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet';
@ -137,16 +138,21 @@ export class WalletsComponent implements OnInit {
currency_code: null as string,
wallet_id: [null as string[]],
});
active = 0;
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFilters, v)),
shareReplay({ refCount: true, bufferSize: 1 }),
);
@ViewChild(FiltersComponent) filters!: FiltersComponent;
typeQp = this.qp.createNamespace<{ isFilter: boolean }>('type');
party$ = this.partyStoreService.party$;
private initFilters = this.filtersForm.value;
constructor(
private fetchWalletsService: FetchWalletsService,
private fetchWalletsTextService: FetchWalletsTextService,
private qp: QueryParamsService<WalletParams>,
private fb: FormBuilder,
private fb: NonNullableFormBuilder,
private walletManagementService: ManagementService,
private log: NotifyLogService,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
@ -168,7 +174,7 @@ export class WalletsComponent implements OnInit {
void this.typeQp.set({ isFilter: !!value });
});
getValueChanges(this.filtersForm)
.pipe(debounceTime(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.qp.set(clean(value));
this.filterSearch();
@ -177,7 +183,6 @@ export class WalletsComponent implements OnInit {
filterSearch(opts?: UpdateOptions) {
const props = clean(this.filtersForm.value);
this.active = countProps(props);
this.partyStoreService.party$.pipe(take(1)).subscribe((p) => {
this.fetchWalletsService.load(
clean({

View File

@ -8,7 +8,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TableModule, ListFieldModule, FiltersModule, SwitchButtonModule } from '@vality/ng-core';
import { AmountCurrencyPipe, PageLayoutModule } from '@cc/app/shared';
import { PageLayoutModule } from '@cc/app/shared';
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
@ -30,7 +30,6 @@ import { WalletsComponent } from './wallets.component';
MerchantFieldModule,
MatButtonModule,
MatIconModule,
AmountCurrencyPipe,
PageLayoutModule,
ListFieldModule,
FiltersModule,

View File

@ -2,7 +2,7 @@
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
<v-filters #filters [active]="active" merge (clear)="filtersForm.reset()">
<v-filters #filters [active]="active$ | async" merge (clear)="filtersForm.reset()">
<ng-template [formGroup]="filtersForm">
<v-date-range-field formControlName="dateRange"></v-date-range-field>
<cc-merchant-field formControlName="merchant"></cc-merchant-field>
@ -35,7 +35,7 @@
[progress]="inProgress$ | async"
rowSelectable
(more)="more()"
(update)="update($event)"
(update)="reload($event)"
>
<v-table-actions>
<button

View File

@ -7,7 +7,6 @@ import {
QueryParamsService,
Column,
UpdateOptions,
countProps,
clean,
DialogService,
DateRange,
@ -17,11 +16,12 @@ import {
createDateRangeToToday,
isEqualDateRange,
getValueChanges,
countChanged,
debounceTimeWithFirst,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import omit from 'lodash-es/omit';
import startCase from 'lodash-es/startCase';
import { debounceTime } from 'rxjs/operators';
import { map, shareReplay } from 'rxjs/operators';
import { WithdrawalParams } from '@cc/app/api/fistful-stat';
@ -65,7 +65,10 @@ export class WithdrawalsComponent implements OnInit {
providerId: null,
terminalId: null,
});
active = 0;
active$ = getValueChanges(this.filtersForm).pipe(
map((v) => countChanged(this.initFilters, v, { dateRange: isEqualDateRange })),
shareReplay({ refCount: true, bufferSize: 1 }),
);
withdrawals$ = this.fetchWithdrawalsService.result$;
inProgress$ = this.fetchWithdrawalsService.isLoading$;
hasMore$ = this.fetchWithdrawalsService.hasMore$;
@ -113,6 +116,8 @@ export class WithdrawalsComponent implements OnInit {
selected: StatWithdrawal[] = [];
statuses: WithdrawalParams['status'][] = ['Pending', 'Succeeded', 'Failed'];
private initFilters = this.filtersForm.value;
constructor(
private fetchWithdrawalsService: FetchWithdrawalsService,
private fb: NonNullableFormBuilder,
@ -127,7 +132,7 @@ export class WithdrawalsComponent implements OnInit {
ngOnInit() {
this.filtersForm.patchValue(Object.assign({}, this.qp.params));
getValueChanges(this.filtersForm)
.pipe(debounceTime(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.update());
}
@ -158,15 +163,16 @@ export class WithdrawalsComponent implements OnInit {
withdrawal_provider_id: providerId,
});
this.fetchWithdrawalsService.load(params, options);
this.active =
countProps(omit(params, 'from_time', 'to_time')) +
+!isEqualDateRange(dateRange, createDateRangeToToday(this.dateRangeDays));
}
more() {
this.fetchWithdrawalsService.more();
}
reload(options: UpdateOptions) {
this.fetchWithdrawalsService.reload(options);
}
adjustment() {
this.dialogService.open(CreateAdjustmentDialogComponent, {
withdrawals: this.selected,

View File

@ -1,4 +1,11 @@
import { Component, Input, AfterViewInit, booleanAttribute, DestroyRef } from '@angular/core';
import {
Component,
Input,
AfterViewInit,
booleanAttribute,
DestroyRef,
inject,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PartyID } from '@vality/domain-proto/domain';
import {
@ -7,19 +14,15 @@ import {
FormControlSuperclass,
createControlProviders,
getValueChanges,
debounceTimeWithFirst,
} from '@vality/ng-core';
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
import {
catchError,
debounceTime,
map,
switchMap,
tap,
distinctUntilChanged,
} from 'rxjs/operators';
import { catchError, map, switchMap, tap, distinctUntilChanged } from 'rxjs/operators';
import { DeanonimusService } from '@cc/app/api/deanonimus';
import { DEBOUNCE_TIME_MS } from '../../../tokens';
@Component({
selector: 'cc-merchant-field',
templateUrl: 'merchant-field.component.html',
@ -39,6 +42,8 @@ export class MerchantFieldComponent
searchChange$ = new Subject<string>();
progress$ = new BehaviorSubject(false);
private debounceTimeMs = inject(DEBOUNCE_TIME_MS);
constructor(
private deanonimusService: DeanonimusService,
private log: NotifyLogService,
@ -55,7 +60,7 @@ export class MerchantFieldComponent
this.options$.next([]);
this.progress$.next(true);
}),
debounceTime(600),
debounceTimeWithFirst(this.debounceTimeMs),
switchMap((term) => this.searchOptions(term)),
takeUntilDestroyed(this.destroyRef),
)

View File

@ -1,14 +1,18 @@
import { Component, Input, OnInit, booleanAttribute } from '@angular/core';
import { PayoutTool } from '@vality/domain-proto/domain';
import { PartyID, ShopID } from '@vality/domain-proto/payment_processing';
import { FormControlSuperclass, Option, createControlProviders } from '@vality/ng-core';
import {
FormControlSuperclass,
Option,
createControlProviders,
NotifyLogService,
handleError,
} from '@vality/ng-core';
import { BehaviorSubject, combineLatest, defer, Observable, of, switchMap } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { handleError, NotificationErrorService } from '../../services/notification-error';
@Component({
selector: 'cc-payout-tool-field',
templateUrl: 'payout-tool-field.component.html',
@ -47,13 +51,7 @@ export class PayoutToolFieldComponent
),
map((contract) => contract.payout_tools),
)
.pipe(
handleError(
this.notificationErrorService.error,
null,
of<PayoutTool[]>([]),
),
)
.pipe(handleError(this.log.error, []))
: of<PayoutTool[]>([]),
),
shareReplay({ refCount: true, bufferSize: 1 }),
@ -61,7 +59,7 @@ export class PayoutToolFieldComponent
constructor(
private partyManagementService: PartyManagementService,
private notificationErrorService: NotificationErrorService,
private log: NotifyLogService,
) {
super();
}

View File

@ -1,7 +1,9 @@
<cc-metadata-form
[extensions]="extensions"
<cc-thrift-editor
[defaultValue]="defaultValue"
[extensions]="extensions$ | async"
[formControl]="control"
[metadata]="metadata$ | async"
[namespace]="namespace"
[namespace]="namespace ?? defaultNamespace"
[noToolbar]="noToolbar"
[type]="type"
></cc-metadata-form>
></cc-thrift-editor>

View File

@ -1,25 +1,21 @@
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { Component } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { ThriftAstMetadata } from '@vality/fistful-proto';
import { FormControlSuperclass, createControlProviders } from '@vality/ng-core';
import { from } from 'rxjs';
import { createControlProviders, getImportValue } from '@vality/ng-core';
import { MetadataFormModule, MetadataFormExtension } from '../../../metadata-form';
import { MetadataFormModule } from '../../../metadata-form';
import { ThriftEditorModule } from '../../../thrift-editor';
import { BaseThriftFormSuperclass } from '../../thrift-forms/utils/thrift-form-superclass';
@Component({
standalone: true,
selector: 'cc-magista-thrift-form',
templateUrl: './magista-thrift-form.component.html',
providers: createControlProviders(() => MagistaThriftFormComponent),
imports: [CommonModule, ReactiveFormsModule, MetadataFormModule],
imports: [CommonModule, ReactiveFormsModule, MetadataFormModule, ThriftEditorModule],
})
export class MagistaThriftFormComponent extends FormControlSuperclass<unknown> {
@Input() namespace: string;
@Input() type: string;
metadata$ = from(
import('@vality/magista-proto/metadata.json').then((m) => m.default as ThriftAstMetadata[]),
);
extensions: MetadataFormExtension[] = [];
export class MagistaThriftFormComponent extends BaseThriftFormSuperclass {
metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/magista-proto/metadata.json'));
defaultNamespace = 'magista';
}

View File

@ -1,89 +0,0 @@
import { formatCurrency, getCurrencySymbol } from '@angular/common';
import {
Pipe,
Inject,
LOCALE_ID,
DEFAULT_CURRENCY_CODE,
PipeTransform,
DestroyRef,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CurrencyObject } from '@vality/domain-proto/domain';
import isNil from 'lodash-es/isNil';
import { ReplaySubject, combineLatest } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { DomainStoreService } from '@cc/app/api/domain-config';
import { toMajor } from '../../../utils';
@Pipe({
standalone: true,
pure: false,
name: 'amountCurrency',
})
export class AmountCurrencyPipe implements PipeTransform {
private params$ = new ReplaySubject<{
amount: number;
currencyCode: string;
format: 'short' | 'long';
}>(1);
private latestValue: string = '';
private isInit = false;
constructor(
@Inject(LOCALE_ID) private _locale: string,
@Inject(DEFAULT_CURRENCY_CODE) private _defaultCurrencyCode: string = 'USD',
private domainStoreService: DomainStoreService,
private destroyRef: DestroyRef,
) {}
init() {
this.isInit = true;
combineLatest([
this.domainStoreService.getObjects('currency').pipe(startWith([] as CurrencyObject[])),
this.params$,
])
.pipe(
map(([currencies, { amount, currencyCode, format }]) => {
if (isNil(amount)) {
return '?';
}
const exponent = currencies.find((c) => c.data.symbolic_code === currencyCode)
?.data?.exponent;
if (!currencyCode || !exponent) {
return formatCurrency(
toMajor(amount, exponent),
this._locale,
'',
'',
format === 'short' ? '0.0-2' : undefined,
);
}
return formatCurrency(
toMajor(amount, exponent),
this._locale,
getCurrencySymbol(currencyCode, 'narrow', this._locale),
currencyCode,
format === 'short' ? '0.0-2' : undefined,
);
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((value) => {
this.latestValue = value;
});
}
transform(
amount: number,
currencyCode: string = this._defaultCurrencyCode,
format: 'short' | 'long' = 'long',
) {
this.params$.next({ amount, currencyCode, format });
if (!this.isInit) {
this.init();
}
return this.latestValue;
}
}

View File

@ -1,4 +1,3 @@
export * from './thrift';
export * from './common';
export * from './value-type-title';
export * from './amount-currency.pipe';

View File

@ -1,43 +1,31 @@
import { Injectable } from '@angular/core';
import { Party } from '@vality/deanonimus-proto/deanonimus';
import { NotifyLogService } from '@vality/ng-core';
import { Observable, of, Subject, defer, BehaviorSubject } from 'rxjs';
import { map, shareReplay, switchMap, debounceTime } from 'rxjs/operators';
import {
NotifyLogService,
handleError,
FetchOptions,
SingleFetchSuperclass,
} from '@vality/ng-core';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
import { DeanonimusService } from '@cc/app/api/deanonimus';
import { progressTo, inProgressFrom } from '@cc/utils';
import { handleError } from './notification-error';
@Injectable()
export class FetchPartiesService {
parties$: Observable<Party[]> = defer(() => this.searchParties$).pipe(
debounceTime(200),
switchMap((text) =>
text
? this.deanonimusService.searchParty(text).pipe(
map((hits) => hits.map((hit) => hit.party)),
handleError(this.log.error, null, of<Party[]>([])),
progressTo(this.progress$),
)
: of([]),
),
shareReplay(1),
);
inProgress$ = inProgressFrom(
() => this.progress$,
() => this.parties$,
);
private progress$ = new BehaviorSubject(0);
private searchParties$: Subject<string> = new Subject();
export class FetchPartiesService extends SingleFetchSuperclass<Party, string> {
constructor(
private deanonimusService: DeanonimusService,
private log: NotifyLogService,
) {}
) {
super();
}
searchParties(text: string) {
this.searchParties$.next(text);
protected fetch(text: string, _options: FetchOptions) {
return text
? this.deanonimusService.searchParty(text).pipe(
map((hits) => ({ result: hits.map((hit) => hit.party) })),
handleError(this.log.error, { result: [] }),
)
: of({ result: [] });
}
}

View File

@ -2,7 +2,6 @@ export * from './app-auth-guard';
export * from './fetch-parties.service';
export * from './keycloak-token-info';
export * from './user-info-based-id-generator';
export * from './partial-fetcher';
export * from './domain-metadata-form-extensions';
export * from './domain-secret-service';
export * from './amount-currency.service';

View File

@ -1,2 +0,0 @@
export * from './notification-error.service';
export * from './utils/handle-error';

View File

@ -1,38 +0,0 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ErrorService } from './types/error-service';
const DEFAULT_DURATION_MS = 6000;
/**
* @deprecated
*/
@Injectable({ providedIn: 'root' })
export class NotificationErrorService implements ErrorService {
constructor(private snackBar: MatSnackBar) {}
error = (error: unknown, clientMessage?: string): void => {
let result: string;
const name = String((error as Record<PropertyKey, unknown>)?.['name'] ?? '');
const message = String((error as Record<PropertyKey, unknown>)?.['message'] ?? '');
if (clientMessage) {
result = clientMessage;
} else {
result = message || name || 'Unknown error';
}
console.warn(
[
`❗️ Caught error: ${result}.`,
name && `Name: ${name}.`,
message && `Message: ${message}.`,
]
.filter(Boolean)
.join(' '),
);
this.snackBar.open(result, 'OK', {
duration: DEFAULT_DURATION_MS,
});
};
}

View File

@ -1,3 +0,0 @@
export interface ErrorService {
error: (error: unknown, message?: string) => void;
}

View File

@ -1,16 +0,0 @@
import { Observable, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
export function handleError<T>(
errorHandler: (error: unknown, message?: string) => void,
message?: string,
result: Observable<T> = EMPTY,
) {
return (source: Observable<T>): Observable<T> =>
source.pipe(
catchError((err) => {
errorHandler(err, message);
return result;
}),
);
}

View File

@ -1 +0,0 @@
export * from './notification.service';

View File

@ -1,24 +0,0 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
const DEFAULT_DURATION_MS = 3000;
/**
* @deprecated
*/
@Injectable({ providedIn: 'root' })
export class NotificationService {
constructor(private snackBar: MatSnackBar) {}
success(message: string = 'Success') {
return this.snackBar.open(message, 'OK', {
duration: DEFAULT_DURATION_MS,
});
}
error(message: string = 'Error') {
return this.snackBar.open(message, 'OK', {
duration: DEFAULT_DURATION_MS,
});
}
}

View File

@ -1,4 +0,0 @@
import { InjectionToken } from '@angular/core';
export const DEBOUNCE_FETCHER_ACTION_TIME = new InjectionToken<number>('debounceFetcherActionTime');
export const DEFAULT_FETCHER_DEBOUNCE_ACTION_TIME = 300;

View File

@ -1,5 +0,0 @@
export interface FetchAction<P = unknown> {
type: 'search' | 'fetchMore';
value?: P;
size?: number;
}

View File

@ -1,9 +0,0 @@
import { Observable } from 'rxjs';
import { FetchResult } from './fetch-result';
export type FetchFn<P, R> = (
params: P,
continuationToken?: string,
size?: number,
) => Observable<FetchResult<R>>;

View File

@ -1,5 +0,0 @@
export interface FetchResult<T> {
result?: T[];
continuationToken?: string;
error?: unknown;
}

View File

@ -1,4 +0,0 @@
export * from './partial-fetcher';
export * from './fetch-result';
export * from './fetch-action';
export * from './consts';

View File

@ -1,2 +0,0 @@
export * from './scan-action';
export * from './scan-search-result';

View File

@ -1,5 +0,0 @@
import { Observable } from 'rxjs';
import { scan } from 'rxjs/operators';
export const scanAction = <T>(s: Observable<T>) =>
s.pipe(scan<T, T>((lastAction, currentAction) => ({ ...lastAction, ...currentAction }), null));

View File

@ -1,48 +0,0 @@
import { Observable, of } from 'rxjs';
import { catchError, first, map, mergeScan } from 'rxjs/operators';
import { FetchAction } from '../fetch-action';
import { FetchFn } from '../fetch-fn';
import { FetchResult } from '../fetch-result';
export const handleFetchResultError =
<R>(result: R[] = [], continuationToken?: string) =>
(s: Observable<FetchResult<R>>): Observable<FetchResult<R>> =>
s.pipe(
catchError((error) =>
of<FetchResult<R>>({
result,
continuationToken,
error,
}),
),
);
export const scanFetchResult =
<P, R>(fn: FetchFn<P, R>, defSize: number) =>
(s: Observable<FetchAction<P>>): Observable<FetchResult<R>> =>
s.pipe(
mergeScan<FetchAction<P>, FetchResult<R>>(
({ result, continuationToken }, { type, value, size }) => {
size = size ?? defSize;
switch (type) {
case 'search':
return fn(value, undefined, size).pipe(
first(),
handleFetchResultError(),
);
case 'fetchMore':
return fn(value, continuationToken, size).pipe(
first(),
map((r) => ({
result: result.concat(r.result),
continuationToken: r.continuationToken,
})),
handleFetchResultError(result, continuationToken),
);
}
},
{ result: [] },
1,
),
);

View File

@ -1,136 +0,0 @@
import { DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EMPTY, merge, Observable, of, Subject } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
filter,
map,
pluck,
share,
shareReplay,
startWith,
switchMap,
tap,
} from 'rxjs/operators';
import { progress } from '@cc/app/shared/custom-operators';
import { FetchAction } from './fetch-action';
import { FetchFn } from './fetch-fn';
import { FetchResult } from './fetch-result';
import { scanAction, scanFetchResult } from './operators';
import { SHARE_REPLAY_CONF } from './utils/share-replay-conf';
// TODO: make free of subscription & UntilDestroy
// TODO: share public props together
// TODO: make fetcher injectable
/**
* @deprecated
*/
export abstract class PartialFetcher<R, P> {
readonly fetchResultChanges$: Observable<{
result: R[];
hasMore: boolean;
continuationToken: string;
}>;
readonly searchResult$: Observable<R[]>;
readonly hasMore$: Observable<boolean>;
readonly doAction$: Observable<boolean>;
readonly doSearchAction$: Observable<boolean>;
readonly errors$: Observable<unknown>;
private action$ = new Subject<FetchAction<P>>();
private destroyRef = inject(DestroyRef);
// TODO: make a dependency for DI
constructor(
debounceActionTime: number = 300,
private size = 25,
) {
const actionWithParams$ = this.getActionWithParams(debounceActionTime);
const fetchResult$ = this.getFetchResult(actionWithParams$);
this.fetchResultChanges$ = fetchResult$.pipe(
map(({ result, continuationToken }) => ({
result: result ?? [],
continuationToken,
hasMore: !!continuationToken,
})),
share(),
);
this.searchResult$ = this.fetchResultChanges$.pipe(
pluck('result'),
shareReplay(SHARE_REPLAY_CONF),
);
this.hasMore$ = this.fetchResultChanges$.pipe(
pluck('hasMore'),
startWith(null as boolean),
distinctUntilChanged(),
shareReplay(SHARE_REPLAY_CONF),
);
this.doAction$ = progress(actionWithParams$, fetchResult$, false).pipe(
shareReplay(SHARE_REPLAY_CONF),
);
this.doSearchAction$ = progress(
actionWithParams$.pipe(filter(({ type }) => type === 'search')),
fetchResult$,
true,
).pipe(shareReplay(SHARE_REPLAY_CONF));
this.errors$ = fetchResult$.pipe(
switchMap(({ error }) => (error ? of(error) : EMPTY)),
tap((error) => this.handleError(error)),
share(),
);
merge(
this.searchResult$,
this.hasMore$,
this.doAction$,
this.doSearchAction$,
this.errors$,
this.fetchResultChanges$,
)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
}
search(value: P, size?: number) {
this.action$.next({ type: 'search', value, size });
}
refresh(size?: number) {
this.action$.next({ type: 'search', size });
}
fetchMore() {
this.action$.next({ type: 'fetchMore' });
}
protected handleError(error: unknown): void {
console.error('Partial fetcher error: ', error);
}
protected abstract fetch(...args: Parameters<FetchFn<P, R>>): ReturnType<FetchFn<P, R>>;
private getActionWithParams(debounceActionTime: number): Observable<FetchAction<P>> {
return this.action$.pipe(
scanAction,
debounceActionTime ? debounceTime(debounceActionTime) : tap(),
share(),
);
}
private getFetchResult(
actionWithParams$: Observable<FetchAction<P>>,
): Observable<FetchResult<R>> {
const fetchFn = this.fetch.bind(this) as FetchFn<P, R>;
return actionWithParams$.pipe(
scanFetchResult(fetchFn, this.size),
shareReplay(SHARE_REPLAY_CONF),
);
}
}

View File

@ -1,4 +0,0 @@
import { ShareReplayConfig } from 'rxjs';
// Default share replay config
export const SHARE_REPLAY_CONF: ShareReplayConfig = { bufferSize: 1, refCount: true };

View File

@ -1,13 +0,0 @@
import { clean, isEmpty } from '@vality/ng-core';
import isObject from 'lodash-es/isObject';
function isEmptyThrift(value: unknown): boolean {
if (isObject(value) && value.constructor === Object) {
return false;
}
return isEmpty(value);
}
export function cleanThrift<T extends object>(obj: T) {
return clean(obj, false, false, (v) => !isEmptyThrift(v));
}

View File

@ -1,2 +1 @@
export * from './clean-thrift';
export * from './table';

View File

@ -2,9 +2,6 @@ import { InjectionToken } from '@angular/core';
import { MatDateFormats } from '@angular/material/core';
import { DATE_QUERY_PARAMS_SERIALIZERS, Serializer } from '@vality/ng-core';
export const SMALL_SEARCH_LIMIT = new InjectionToken<number>('smallSearchLimit');
export const DEFAULT_SMALL_SEARCH_LIMIT = 5;
export const DEFAULT_QUERY_PARAMS_SERIALIZERS: Serializer[] = DATE_QUERY_PARAMS_SERIALIZERS;
export const DEFAULT_MAT_DATE_FORMATS: MatDateFormats = {
@ -20,7 +17,7 @@ export const DEFAULT_MAT_DATE_FORMATS: MatDateFormats = {
};
export const DATE_RANGE_DAYS = new InjectionToken<number>('DATE_RANGE_DAYS');
export const DEFAULT_DATE_RANGE_DAYS: number = 30;
export const DEFAULT_DATE_RANGE_DAYS: number = 1;
export const DEBOUNCE_TIME_MS = new InjectionToken<number>('DEBOUNCE_TIME_MS');
export const DEFAULT_DEBOUNCE_TIME_MS: number = 300;
export const DEFAULT_DEBOUNCE_TIME_MS: number = 500;

View File

@ -1,43 +0,0 @@
import { ValuesType } from 'utility-types';
export function getEnumEntries<E extends Record<PropertyKey, unknown>>(
srcEnum: E,
): [key: keyof E, value: ValuesType<E>][] {
const entries = Object.entries(srcEnum);
if (!entries.length) {
return [];
}
const isValueNumberEnum = entries.some(([, v]) => typeof v === 'number');
if (isValueNumberEnum) {
return entries.filter(([, v]) => typeof v === 'number') as never;
}
return entries as never;
}
export function getEnumKeyValues<E extends Record<PropertyKey, unknown>>(
srcEnum: E,
): { key: keyof E; value: ValuesType<E> }[] {
return getEnumEntries(srcEnum).map(([key, value]) => ({ key, value }));
}
export function getEnumKeys<E extends Record<PropertyKey, unknown>>(srcEnum: E): (keyof E)[] {
return getEnumEntries(srcEnum).map(([k]) => k);
}
export function getEnumValues<E extends Record<PropertyKey, unknown>>(srcEnum: E): ValuesType<E>[] {
return getEnumEntries(srcEnum).map(([, v]) => v);
}
export function getEnumKey<E extends Record<PropertyKey, unknown>>(
srcEnum: E,
value: ValuesType<E>,
): keyof E {
return getEnumKeyValues(srcEnum).find((e) => String(e.value) === String(value))?.key;
}
export function enumHasValue<E extends Record<PropertyKey, unknown>>(
srcEnum: E,
value: ValuesType<E> | string,
): value is ValuesType<E> {
return getEnumValues(srcEnum).includes(value as ValuesType<E>);
}

View File

@ -1,14 +1,9 @@
export * from './get-union-key';
export * from './remove-empty-properties';
export * from './wrap-values-to-array';
export * from './thrift-json-converter';
export * from './to-minor';
export * from './to-major';
export * from './thrift-utils';
export * from './has-active-fragments';
export * from './poll';
export * from './operators';
export * from './get-enum-keys';
export * from './enumerate';
export * from './thrift-instance';
export * from './csv';

View File

@ -1,11 +0,0 @@
import { merge, Observable, MonoTypeOperatorFunction, EMPTY, mergeMap } from 'rxjs';
import { ObservableOrFn, getObservable } from './get-observable';
export function attach<T>(attached: ObservableOrFn<T>, main: ObservableOrFn): Observable<T> {
return merge(getObservable(attached), getObservable(main).pipe(mergeMap(() => EMPTY)));
}
export function attachTo<T>(main: ObservableOrFn): MonoTypeOperatorFunction<T> {
return (src$) => attach(src$, main);
}

View File

@ -1,7 +0,0 @@
import { Observable, defer } from 'rxjs';
export type ObservableOrFn<T = unknown> = (() => Observable<T>) | Observable<T>;
export function getObservable<T>(obsOrFn: ObservableOrFn<T>): Observable<T> {
return typeof obsOrFn === 'function' ? defer(obsOrFn) : obsOrFn;
}

View File

@ -1,16 +0,0 @@
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
/**
* Should be called once
* It's better to use before the operator "share"
*/
export function handleError(handle: (error: unknown) => void) {
return <T>(src$: Observable<T>) =>
src$.pipe(
catchError((error) => {
handle(error);
return throwError(error);
}),
);
}

View File

@ -1,13 +0,0 @@
import { map, delay, share } from 'rxjs/operators';
import { attach } from './attach';
import { getObservable, ObservableOrFn } from './get-observable';
export function inProgressFrom(progress: ObservableOrFn<number>, main?: ObservableOrFn) {
return (main ? attach(progress, main) : getObservable(progress)).pipe(
map(Boolean),
// make async to bypass angular detect changes
delay(0),
share(),
);
}

View File

@ -1,3 +0,0 @@
export * from './progress-to';
export * from './handle-error';
export * from './in-progress-from';

View File

@ -1,13 +0,0 @@
import { BehaviorSubject, defer, MonoTypeOperatorFunction, isObservable } from 'rxjs';
import { finalize } from 'rxjs/operators';
export function progressTo<T>(
subject$: BehaviorSubject<number> | (() => BehaviorSubject<number>),
): MonoTypeOperatorFunction<T> {
const getSub = () => (isObservable(subject$) ? subject$ : subject$());
return (src$) =>
defer(() => {
getSub().next(getSub().getValue() + 1);
return src$.pipe(finalize(() => getSub().next(getSub().getValue() - 1)));
});
}

View File

@ -1,4 +0,0 @@
import identity from 'lodash-es/identity';
import pickBy from 'lodash-es/pickBy';
export const removeEmptyProperties = <T extends object>(s: T) => pickBy(s, identity) as Partial<T>;

View File

@ -1,9 +0,0 @@
import isNil from 'lodash-es/isNil';
import round from 'lodash-es/round';
export const toMajor = (amount: number, exponent = 2): number | null => {
if (isNil(amount)) {
return null;
}
return round(amount / 10 ** exponent, exponent);
};

View File

@ -1,2 +0,0 @@
export const toMinor = (amount: number, exponent = 2): number =>
Math.round(amount * 10 ** exponent);