diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f9c1097b..2af487ca 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -12,7 +12,7 @@ jobs: - name: Build run: npm run build - name: Deploy image - uses: valitydev/action-deploy-docker@v2.0.3 + uses: valitydev/action-deploy-docker@v2 with: registry-username: ${{ github.actor }} registry-access-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/package-lock.json b/package-lock.json index fb5af8c2..819acf18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "@vality/magista-proto": "1.0.1-9f37374.0", "@vality/messages-proto": "1.0.1-8c5435c.0", "@vality/payout-manager-proto": "1.0.1-dbed280.0", - "@vality/repairer-proto": "1.0.1-8ccd6f7.0", + "@vality/repairer-proto": "1.0.1-675b6f4.0", "@vality/thrift-ts": "2.2.2-e8bc2ab.0", "@vality/woody": "0.1.1", "angular-file": "3.6.0", @@ -5528,9 +5528,9 @@ "integrity": "sha512-uprNZIFMyLZbFg1TNVelr961pJoNDWUindLl6uhKwzvfSGOkGyko0XPQlD6WTziM7v4ctvsRkLf6xdPnkznjaA==" }, "node_modules/@vality/repairer-proto": { - "version": "1.0.1-8ccd6f7.0", - "resolved": "https://registry.npmjs.org/@vality/repairer-proto/-/repairer-proto-1.0.1-8ccd6f7.0.tgz", - "integrity": "sha512-wJY7fpXONM6fWXN5cAnw1by24aSq7AcsotNBWNbAF7lDtq/xDGZoIpEHut7VCbZOUfdK0pYkzVL0FNRYTVyFrA==" + "version": "1.0.1-675b6f4.0", + "resolved": "https://registry.npmjs.org/@vality/repairer-proto/-/repairer-proto-1.0.1-675b6f4.0.tgz", + "integrity": "sha512-l5a9e7HCKD803xRgDlpIhhQUhb+JWXTPRb2ek0c1Ky4g2kkmBLB4PomBUKYiDjdHGo4tHO9IPBPTCDZVCl1cOA==" }, "node_modules/@vality/thrift-ts": { "version": "2.2.2-e8bc2ab.0", @@ -25935,9 +25935,9 @@ "integrity": "sha512-uprNZIFMyLZbFg1TNVelr961pJoNDWUindLl6uhKwzvfSGOkGyko0XPQlD6WTziM7v4ctvsRkLf6xdPnkznjaA==" }, "@vality/repairer-proto": { - "version": "1.0.1-8ccd6f7.0", - "resolved": "https://registry.npmjs.org/@vality/repairer-proto/-/repairer-proto-1.0.1-8ccd6f7.0.tgz", - "integrity": "sha512-wJY7fpXONM6fWXN5cAnw1by24aSq7AcsotNBWNbAF7lDtq/xDGZoIpEHut7VCbZOUfdK0pYkzVL0FNRYTVyFrA==" + "version": "1.0.1-675b6f4.0", + "resolved": "https://registry.npmjs.org/@vality/repairer-proto/-/repairer-proto-1.0.1-675b6f4.0.tgz", + "integrity": "sha512-l5a9e7HCKD803xRgDlpIhhQUhb+JWXTPRb2ek0c1Ky4g2kkmBLB4PomBUKYiDjdHGo4tHO9IPBPTCDZVCl1cOA==" }, "@vality/thrift-ts": { "version": "2.2.2-e8bc2ab.0", diff --git a/package.json b/package.json index 1e8d54ff..f40c98bc 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@vality/magista-proto": "1.0.1-9f37374.0", "@vality/messages-proto": "1.0.1-8c5435c.0", "@vality/payout-manager-proto": "1.0.1-dbed280.0", - "@vality/repairer-proto": "1.0.1-8ccd6f7.0", + "@vality/repairer-proto": "1.0.1-675b6f4.0", "@vality/thrift-ts": "2.2.2-e8bc2ab.0", "@vality/woody": "0.1.1", "angular-file": "3.6.0", diff --git a/src/app/api/repairer/repair-management.service.ts b/src/app/api/repairer/repair-management.service.ts index e89c4b61..a326cc74 100644 --- a/src/app/api/repairer/repair-management.service.ts +++ b/src/app/api/repairer/repair-management.service.ts @@ -13,7 +13,7 @@ export class RepairManagementService extends createThriftApi() { constructor(injector: Injector) { super(injector, { service, - path: '/v1/repairer', // TODO + path: '/v1/repair', metadata: () => import('@vality/repairer-proto/lib/metadata.json').then((m) => m.default), context, diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 40edbdb8..2d5cd56b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -48,7 +48,16 @@ export class AppComponent implements OnInit { activateRoles: [PaymentAdjustmentRole.Create], }, { name: 'Merchants', route: '/parties', activateRoles: [PartyRole.Get] }, - { name: 'Repairing', route: '/repairing', activateRoles: [DomainConfigRole.Checkout] }, + { + name: 'Repairing', + route: '/old-repairing', + activateRoles: [DomainConfigRole.Checkout], + }, + { + name: 'New repairing', + route: '/repairing', + activateRoles: [DomainConfigRole.Checkout], + }, { name: 'Operations', route: '/operations', diff --git a/src/app/repairing/repairing-routing.module.ts b/src/app/repairing/repairing-routing.module.ts index 1ba4ecc9..d744558a 100644 --- a/src/app/repairing/repairing-routing.module.ts +++ b/src/app/repairing/repairing-routing.module.ts @@ -9,7 +9,7 @@ import { RepairingComponent } from './repairing.component'; imports: [ RouterModule.forChild([ { - path: 'repairing', + path: 'old-repairing', component: RepairingComponent, canActivate: [AppAuthGuardService], data: { diff --git a/src/app/repairing/repairing.component.ts b/src/app/repairing/repairing.component.ts index 96684df6..6b286ac0 100644 --- a/src/app/repairing/repairing.component.ts +++ b/src/app/repairing/repairing.component.ts @@ -7,7 +7,6 @@ import { RepairingService } from './repairing.service'; @Component({ templateUrl: 'repairing.component.html', styleUrls: ['repairing.component.scss'], - providers: [], }) export class RepairingComponent { progress$: Observable; diff --git a/src/app/sections/repairing/components/repair-by-scenario-dialog/repair-by-scenario-dialog.component.html b/src/app/sections/repairing/components/repair-by-scenario-dialog/repair-by-scenario-dialog.component.html new file mode 100644 index 00000000..d389ee2d --- /dev/null +++ b/src/app/sections/repairing/components/repair-by-scenario-dialog/repair-by-scenario-dialog.component.html @@ -0,0 +1,30 @@ + +
+ + Invoices + Withdrawals + + + +
+ + + +
diff --git a/src/app/sections/repairing/components/repair-by-scenario-dialog/repair-by-scenario-dialog.component.ts b/src/app/sections/repairing/components/repair-by-scenario-dialog/repair-by-scenario-dialog.component.ts new file mode 100644 index 00000000..eb977ec3 --- /dev/null +++ b/src/app/sections/repairing/components/repair-by-scenario-dialog/repair-by-scenario-dialog.component.ts @@ -0,0 +1,64 @@ +import { Component, Injector } from '@angular/core'; +import { Validators } from '@angular/forms'; +import { FormControl } from '@ngneat/reactive-forms'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { RepairInvoicesRequest, RepairWithdrawalsRequest } from '@vality/repairer-proto'; +import { BehaviorSubject, from } from 'rxjs'; + +import { + BaseDialogResponseStatus, + BaseDialogSuperclass, +} from '../../../../../components/base-dialog'; +import { progressTo } from '../../../../../utils'; +import { RepairManagementService } from '../../../../api/repairer'; +import { ErrorService } from '../../../../shared/services/error'; +import { NotificationService } from '../../../../shared/services/notification'; + +enum Types { + Invoices, + Withdrawals, +} + +@UntilDestroy() +@Component({ + templateUrl: './repair-by-scenario-dialog.component.html', +}) +export class RepairByScenarioDialogComponent extends BaseDialogSuperclass { + typeControl = new FormControl(null, Validators.required); + form = new FormControl( + null, + Validators.required + ); + metadata$ = from(import('@vality/repairer-proto/lib/metadata.json').then((m) => m.default)); + progress$ = new BehaviorSubject(0); + typesEnum = Types; + + constructor( + injector: Injector, + private repairManagementService: RepairManagementService, + private errorService: ErrorService, + private notificationService: NotificationService + ) { + super(injector); + } + + repair() { + (this.typeControl.value === Types.Invoices + ? this.repairManagementService.RepairInvoices(this.form.value as RepairInvoicesRequest) + : this.repairManagementService.RepairWithdrawals( + this.form.value as RepairWithdrawalsRequest + ) + ) + .pipe(progressTo(this.progress$), untilDestroyed(this)) + .subscribe({ + next: () => { + this.notificationService.success(); + this.dialogRef.close({ status: BaseDialogResponseStatus.Success }); + }, + error: (err) => { + this.errorService.error(err); + this.notificationService.error(); + }, + }); + } +} diff --git a/src/app/sections/repairing/repairing-routing.module.ts b/src/app/sections/repairing/repairing-routing.module.ts new file mode 100644 index 00000000..22f3388c --- /dev/null +++ b/src/app/sections/repairing/repairing-routing.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppAuthGuardService } from '../../shared/services'; +import { RepairingComponent } from './repairing.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: RepairingComponent, + canActivate: [AppAuthGuardService], + data: { + roles: [], + }, + }, + ]), + ], + exports: [RouterModule], +}) +export class RepairingRoutingModule {} diff --git a/src/app/sections/repairing/repairing.component.html b/src/app/sections/repairing/repairing.component.html new file mode 100644 index 00000000..ff035516 --- /dev/null +++ b/src/app/sections/repairing/repairing.component.html @@ -0,0 +1,127 @@ +
+

Repairing

+
+ + + + IDs + + id0,id1 + + + Namespace + + + + + Provider ID + + + + Status + + any + + {{ statusName }} + + + + + Error message + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{{ i.id }}Namespace{{ i.ns }}Created at + {{ i.created_at | date: 'dd.MM.yyyy HH:mm:ss' }} + + + Status + + + {{ statusNameByValue[i.status] }} + Provider + {{ i.provider_id }} + + History + + {{ i.history?.length || '' }} +
+ + +
+
+
+
diff --git a/src/app/sections/repairing/repairing.component.ts b/src/app/sections/repairing/repairing.component.ts new file mode 100644 index 00000000..40461e12 --- /dev/null +++ b/src/app/sections/repairing/repairing.component.ts @@ -0,0 +1,152 @@ +import { SelectionModel } from '@angular/cdk/collections'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { DateRange } from '@angular/material/datepicker'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { Machine, Namespace, ProviderID, RepairStatus } from '@vality/repairer-proto'; +import isEmpty from 'lodash-es/isEmpty'; +import isNil from 'lodash-es/isNil'; +import omitBy from 'lodash-es/omitBy'; +import { Moment } from 'moment'; +import { filter, map, switchMap } from 'rxjs/operators'; + +import { BaseDialogResponseStatus } from '../../../components/base-dialog'; +import { BaseDialogService } from '../../../components/base-dialog/services/base-dialog.service'; +import { ConfirmActionDialogComponent } from '../../../components/confirm-action-dialog'; +import { getEnumKeys } from '../../../utils'; +import { RepairManagementService } from '../../api/repairer'; +import { QueryParamsService } from '../../shared/services'; +import { ErrorService } from '../../shared/services/error'; +import { NotificationService } from '../../shared/services/notification'; +import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component'; +import { MachinesService } from './services/machines.service'; + +interface Filters { + ids: string; + ns: Namespace; + timespan: DateRange; + provider_id: ProviderID; + status: RepairStatus; + error_message: string; +} + +@UntilDestroy() +@Component({ + selector: 'cc-repairing', + templateUrl: './repairing.component.html', + styles: [ + ` + :host { + display: block; + padding: 24px 16px; + } + `, + ], + providers: [MachinesService], +}) +export class RepairingComponent implements OnInit { + machines$ = this.machinesService.searchResult$; + inProgress$ = this.machinesService.doAction$; + hasMore$ = this.machinesService.hasMore$; + filters = this.fb.group({ + ids: null, + ns: null, + timespan: null, + provider_id: null, + status: null, + error_message: null, + }); + selection: SelectionModel; + displayedColumns = ['_select', 'id', 'namespace', 'createdAt', 'provider', 'status', 'history']; + statusNameByValue = Object.fromEntries(Object.entries(RepairStatus).map(([k, v]) => [v, k])); + status = RepairStatus; + statuses = getEnumKeys(RepairStatus); + + constructor( + private machinesService: MachinesService, + private fb: FormBuilder, + private qp: QueryParamsService, + private baseDialogService: BaseDialogService, + private repairManagementService: RepairManagementService, + private notificationService: NotificationService, + private errorService: ErrorService + ) {} + + ngOnInit() { + this.filters.valueChanges + .pipe( + map(() => { + return omitBy(this.filters.value, isEmpty); + }), + untilDestroyed(this) + ) + .subscribe((v: Filters) => this.qp.set(v)); + this.qp.params$ + .pipe( + map(({ ids, ns, timespan: d, provider_id, status, error_message }) => { + const timespan = omitBy( + { + from_time: d?.start?.toISOString(), + to_time: d?.end?.toISOString(), + }, + isNil + ); + return omitBy( + { + ids: ids?.split(/[,.;\s]/)?.filter(Boolean), + ns, + provider_id, + status, + error_message, + timespan: Object.keys(timespan).length ? timespan : null, + }, + isEmpty + ); + }), + untilDestroyed(this) + ) + .subscribe((params) => this.machinesService.search(params)); + } + + update() { + this.machinesService.refresh(); + } + + fetchMore() { + this.machinesService.fetchMore(); + } + + repair() { + this.baseDialogService + .open(ConfirmActionDialogComponent, { + title: `Simple repair ${this.selection.selected.length} machines`, + }) + .afterClosed() + .pipe( + filter(({ status }) => status === BaseDialogResponseStatus.Success), + switchMap(() => + this.repairManagementService.SimpleRepairAll( + this.selection.selected.map(({ id, ns }) => ({ id, ns })) + ) + ), + untilDestroyed(this) + ) + .subscribe({ + next: () => { + this.notificationService.success(); + }, + error: (err) => { + this.notificationService.error(); + this.errorService.error(err); + }, + }); + } + + repairByScenario() { + this.baseDialogService + .open(RepairByScenarioDialogComponent) + .afterClosed() + .pipe(untilDestroyed(this)) + .subscribe(); + } +} diff --git a/src/app/sections/repairing/repairing.module.ts b/src/app/sections/repairing/repairing.module.ts new file mode 100644 index 00000000..bb2836e3 --- /dev/null +++ b/src/app/sections/repairing/repairing.module.ts @@ -0,0 +1,51 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +import { ActionsModule } from '../../../components/actions'; +import { BaseDialogModule } from '../../../components/base-dialog'; +import { EmptySearchResultModule } from '../../../components/empty-search-result'; +import { TableModule } from '../../../components/table'; +import { MetadataFormModule } from '../../shared'; +import { DateRangeModule } from '../../shared/components/date-range/date-range.module'; +import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component'; +import { RepairingRoutingModule } from './repairing-routing.module'; +import { RepairingComponent } from './repairing.component'; + +@NgModule({ + imports: [ + CommonModule, + RepairingRoutingModule, + TableModule, + MatCardModule, + ReactiveFormsModule, + FlexLayoutModule, + MatProgressBarModule, + MatButtonModule, + EmptySearchResultModule, + MatTableModule, + MatTooltipModule, + MatBadgeModule, + MatFormFieldModule, + MatInputModule, + DateRangeModule, + MatSelectModule, + ActionsModule, + BaseDialogModule, + MetadataFormModule, + MatRadioModule, + ], + declarations: [RepairingComponent, RepairByScenarioDialogComponent], +}) +export class RepairingModule {} diff --git a/src/app/sections/repairing/services/machines.service.ts b/src/app/sections/repairing/services/machines.service.ts new file mode 100644 index 00000000..29f62764 --- /dev/null +++ b/src/app/sections/repairing/services/machines.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Machine, SearchRequest } from '@vality/repairer-proto'; +import { map } from 'rxjs/operators'; + +import { RepairManagementService } from '../../../api/repairer'; +import { PartialFetcher } from '../../../shared/services'; + +@Injectable() +export class MachinesService extends PartialFetcher { + constructor(private repairManagementService: RepairManagementService) { + super(); + } + + protected fetch(params: SearchRequest, continuationToken: string) { + return this.repairManagementService + .Search({ limit: 100, continuation_token: continuationToken, ...params }) + .pipe( + map(({ machines, continuation_token }) => ({ + result: machines, + continuationToken: continuation_token, + })) + ); + } +} diff --git a/src/app/sections/sections-routing.module.ts b/src/app/sections/sections-routing.module.ts index 8c1c5845..0c021739 100644 --- a/src/app/sections/sections-routing.module.ts +++ b/src/app/sections/sections-routing.module.ts @@ -11,6 +11,10 @@ const ROUTES: Routes = [ loadChildren: () => import('./withdrawals/withdrawals.module').then((m) => m.WithdrawalsModule), }, + { + path: 'repairing', + loadChildren: () => import('./repairing/repairing.module').then((m) => m.RepairingModule), + }, ]; @NgModule({ diff --git a/src/components/actions/actions.component.scss b/src/components/actions/actions.component.scss index facc7e42..9301bcfe 100644 --- a/src/components/actions/actions.component.scss +++ b/src/components/actions/actions.component.scss @@ -1,8 +1,14 @@ -.cc-actions { - ::ng-deep & > *:last-child { - margin-left: auto; +::ng-deep .cc-actions { + & > *:first-child:not(:last-child) { + margin-right: auto !important; @media screen and (max-width: 959px) { - margin-left: initial; + margin-right: initial !important; + } + } + & > *:first-child:last-child { + margin-left: auto !important; + @media screen and (max-width: 959px) { + margin-left: initial !important; } } }