FE-954: claim management API integration (#89)

This commit is contained in:
Alexandra Usacheva 2019-12-16 19:22:16 +03:00 committed by GitHub
parent f42a011eff
commit 3374d55ee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 432 additions and 197 deletions

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
@rbkmoney:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}

4
Jenkinsfile vendored
View File

@ -15,7 +15,9 @@ build('control-center', 'docker-host') {
def pipeline = {
runStage('init') {
withGithubSshCredentials {
sh 'make wc_init'
withGithubToken {
sh 'make wc_init'
}
}
}
runStage('build') {

View File

@ -20,7 +20,7 @@ BASE_IMAGE_TAG := 2b4570bc1d9631c10aaed2132eb87eb9003f3471
BUILD_IMAGE_TAG := f3732d29a5e622aabf80542b5138b3631a726adb
GIT_SSH_COMMAND :=
DOCKER_RUN_OPTS = -e GIT_SSH_COMMAND='$(GIT_SSH_COMMAND)' -e NG_CLI_ANALYTICS=ci
DOCKER_RUN_OPTS = -e GIT_SSH_COMMAND='$(GIT_SSH_COMMAND)' -e NG_CLI_ANALYTICS=ci -e NPM_TOKEN='$(GITHUB_TOKEN)'
CALL_W_CONTAINER := init build clean submodules
@ -53,7 +53,7 @@ clean:
compile-damsel: damsel-client damsel-model damsel-meta
damsel-client:
@$(foreach file,domain_config payment_processing merch_stat,echo $(file); thrift -r -gen js:node,runtime_package=woody_js/dist/thrift -o ./src/app/thrift ./node_modules/damsel/proto/$(file).thrift;)
@$(foreach file,domain_config payment_processing merch_stat claim_management,echo $(file); thrift -r -gen js:node,runtime_package=woody_js/dist/thrift -o ./src/app/thrift ./node_modules/damsel/proto/$(file).thrift;)
damsel-meta:
npm run damsel-meta

62
package-lock.json generated
View File

@ -2402,6 +2402,12 @@
}
}
},
"@rbkmoney/partial-fetcher": {
"version": "1.0.4",
"requires": {
"rxjs": "^6.5.3"
}
},
"@schematics/angular": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.6.tgz",
@ -2477,6 +2483,12 @@
"@types/jasmine": "*"
}
},
"@types/jwt-decode": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/jwt-decode/-/jwt-decode-2.2.1.tgz",
"integrity": "sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A==",
"dev": true
},
"@types/lodash": {
"version": "4.14.116",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz",
@ -5222,8 +5234,8 @@
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
},
"damsel": {
"version": "git+ssh://git@github.com/rbkmoney/damsel.git#b563890354447a5e175a9a318b33233a926a5e9c",
"from": "git+ssh://git@github.com/rbkmoney/damsel.git#b563890354447a5e175a9a318b33233a926a5e9c"
"version": "git+ssh://git@github.com/rbkmoney/damsel.git#5a78a602632a1dbbc5f294361d1538cff4a27040",
"from": "git+ssh://git@github.com/rbkmoney/damsel.git#5a78a602632a1dbbc5f294361d1538cff4a27040"
},
"dashdash": {
"version": "1.14.1",
@ -7884,6 +7896,11 @@
}
}
},
"jwt-decode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
"integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
},
"karma": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/karma/-/karma-4.3.0.tgz",
@ -11755,27 +11772,6 @@
"integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
"dev": true
},
"uglify-es": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
"integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
"requires": {
"commander": "~2.13.0",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
"integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"uglify-js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
@ -11838,6 +11834,11 @@
"y18n": "^4.0.0"
}
},
"commander": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
"integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA=="
},
"find-cache-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
@ -11954,6 +11955,15 @@
"requires": {
"safe-buffer": "^5.1.1"
}
},
"uglify-es": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
"integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
"requires": {
"commander": "~2.13.0",
"source-map": "~0.6.1"
}
}
}
},
@ -14068,8 +14078,8 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"woody_js": {
"version": "git+ssh://git@github.com/rbkmoney/woody_js.git#d2c90a54861593fa794afe3b09c4febb9d6e37d0",
"from": "git+ssh://git@github.com/rbkmoney/woody_js.git#d2c90a54861593fa794afe3b09c4febb9d6e37d0",
"version": "git+ssh://git@github.com/rbkmoney/woody_js.git#bc2c9f86cae470a0fe99730ca4a30b204058214b",
"from": "git+ssh://git@github.com/rbkmoney/woody_js.git#bc2c9f86cae470a0fe99730ca4a30b204058214b",
"requires": {
"babel-core": "6.26.3",
"babel-loader": "7.1.4",
@ -14271,7 +14281,7 @@
},
"stream-http": {
"version": "2.3.1",
"resolved": "http://registry.npmjs.org/stream-http/-/stream-http-2.3.1.tgz",
"resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.3.1.tgz",
"integrity": "sha1-fh3IcQLD4xsy5mDwTKMfI929HVI=",
"requires": {
"builtin-status-codes": "^2.0.0",

View File

@ -12,6 +12,7 @@
"damsel-meta": "thrift-ts node_modules/damsel/proto -o src/assets/meta-damsel.json --json --pack --prettify",
"machinegun-model": "thrift-ts node_modules/machinegun_proto/proto -o src/app/machinegun/gen-model -d false",
"fistful-model": "thrift-ts node_modules/fistful-proto/proto -o src/app/fistful/gen-model -d false",
"codegen": "npm run damsel-model; npm run damsel-meta; npm run machinegun-model; npm run fistful-model",
"prettier": "prettier \"**/*.{html,js,ts,css,md,json,prettierrc,svg}\" --write",
"check": "prettier \"**/*.{html,js,ts,css,md,json,prettierrc,svg}\" --list-different"
},
@ -30,12 +31,14 @@
"@angular/platform-browser-dynamic": "^8.2.8",
"@angular/platform-server": "^8.2.8",
"@angular/router": "^8.2.8",
"@rbkmoney/partial-fetcher": "^1.0.4",
"angular2-prettyjson": "3.0.1",
"core-js": "^2.5.4",
"damsel": "git+ssh://git@github.com/rbkmoney/damsel.git#b563890354447a5e175a9a318b33233a926a5e9c",
"damsel": "git+ssh://git@github.com/rbkmoney/damsel.git#5a78a602632a1dbbc5f294361d1538cff4a27040",
"fistful-proto": "git+ssh://git@github.com/rbkmoney/fistful-proto.git#59b72ba54ec0e7f1af1d9b83aba10883ac985019",
"hammerjs": "^2.0.8",
"jsonc-parser": "^2.0.2",
"jwt-decode": "^2.2.0",
"keycloak-angular": "6.0.0",
"keycloak-js": "4.5.0",
"lodash-es": "^4.17.10",
@ -45,7 +48,7 @@
"rxjs": "^6.5.3",
"thrift-ts": "git+ssh://git@github.com/rbkmoney/thrift-ts.git#a5e3830ad30d5717e5e627ddcefa8f4d8918b174",
"uuid": "^3.3.2",
"woody_js": "git+ssh://git@github.com/rbkmoney/woody_js.git#d2c90a54861593fa794afe3b09c4febb9d6e37d0",
"woody_js": "git+ssh://git@github.com/rbkmoney/woody_js.git#bc2c9f86cae470a0fe99730ca4a30b204058214b",
"zone.js": "~0.8.26"
},
"devDependencies": {
@ -54,6 +57,7 @@
"@angular/compiler-cli": "^8.2.8",
"@types/jasmine": "~2.8.6",
"@types/jasminewd2": "~2.0.3",
"@types/jwt-decode": "^2.2.1",
"@types/lodash-es": "^4.17.1",
"@types/node": "~8.9.4",
"@types/uuid": "^3.4.3",

View File

@ -20,7 +20,6 @@ import {
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { ClaimsModule } from './claims/claims.module';
import { AppRoutingModule } from './app-routing.module';
import { ClaimModule } from './claim/claim.module';
import { PayoutsModule } from './payouts/payouts.module';
@ -30,6 +29,7 @@ import { PartyModule } from './party/party.module';
import { DomainModule } from './domain';
import { RepairingModule } from './repairing/repairing.module';
import { DepositsModule } from './deposits/deposits.module';
import { ClaimMgtModule } from './claim-mgt/claim-mgt.module';
@NgModule({
declarations: [AppComponent],
@ -44,8 +44,8 @@ import { DepositsModule } from './deposits/deposits.module';
MatMenuModule,
MatSidenavModule,
MatListModule,
ClaimsModule,
ClaimModule,
ClaimMgtModule,
PayoutsModule,
PaymentAdjustmentModule,
PartiesModule,

View File

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppAuthGuardService } from '../app-auth-guard.service';
@NgModule({
imports: [
RouterModule.forChild([
{
path: 'claims',
loadChildren: () => import('./claims').then(m => m.ClaimsModule),
canActivate: [AppAuthGuardService],
data: {
roles: ['claim:get']
}
},
{
path: 'party',
loadChildren: () => import('./claim').then(m => m.ClaimModule),
canActivate: [AppAuthGuardService],
data: {
roles: ['claim:get']
}
}
])
],
exports: [RouterModule]
})
export class ClaimMgtRouting {}

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { ClaimMgtRouting } from './claim-mgt-routing.module';
@NgModule({
imports: [ClaimMgtRouting]
})
export class ClaimMgtModule {}

View File

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppAuthGuardService } from '../../app-auth-guard.service';
import { ClaimComponent } from './claim.component';
@NgModule({
imports: [
RouterModule.forChild([
{
path: ':party_id/claim/:claim_id',
component: ClaimComponent,
canActivate: [AppAuthGuardService],
data: {
roles: ['claim:get']
}
}
])
],
exports: [RouterModule]
})
export class ClaimRoutingModule {}

View File

@ -0,0 +1 @@
Claim

View File

@ -0,0 +1,6 @@
import { Component } from '@angular/core';
@Component({
templateUrl: 'claim.component.html'
})
export class ClaimComponent {}

View File

@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { ClaimComponent } from './claim.component';
import { ClaimRoutingModule } from './claim-routing.module';
@NgModule({
imports: [ClaimRoutingModule],
declarations: [ClaimComponent]
})
export class ClaimModule {}

View File

@ -0,0 +1 @@
export * from './claim.module';

View File

@ -1,14 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ClaimsComponent } from './claims.component';
import { AppAuthGuardService } from '../app-auth-guard.service';
import { AppAuthGuardService } from '../../app-auth-guard.service';
@NgModule({
imports: [
RouterModule.forChild([
{
path: 'claims',
path: '',
component: ClaimsComponent,
canActivate: [AppAuthGuardService],
data: {

View File

@ -1,15 +1,15 @@
<table mat-table [dataSource]="claims">
<ng-container matColumnDef="partyID">
<th mat-header-cell *matHeaderCellDef>Party ID</th>
<td mat-cell *matCellDef="let claim">{{ claim.partyId }}</td>
<td mat-cell *matCellDef="let claim">{{ claim.party_id }}</td>
</ng-container>
<ng-container matColumnDef="claimID">
<th fxHide.sm fxHide.xs mat-header-cell *matHeaderCellDef>Claim ID</th>
<td fxHide.sm fxHide.xs mat-cell *matCellDef="let claim">{{ claim.claimId }}</td>
<td fxHide.sm fxHide.xs mat-cell *matCellDef="let claim">{{ claim.id }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let claim">{{ claim.status }}</td>
<td mat-cell *matCellDef="let claim">{{ getClaimStatus(claim.status) }}</td>
</ng-container>
<ng-container matColumnDef="revision">
<th fxHide.sm fxHide.xs mat-header-cell *matHeaderCellDef>Revision</th>
@ -18,19 +18,19 @@
<ng-container matColumnDef="createdAt">
<th fxHide.xs mat-header-cell *matHeaderCellDef>Created at</th>
<td fxHide.xs mat-cell *matCellDef="let claim">
{{ claim.createdAt | date: 'dd.MM.yyyy HH:mm:ss' }}
{{ claim.created_at | date: 'dd.MM.yyyy HH:mm:ss' }}
</td>
</ng-container>
<ng-container matColumnDef="updatedAt">
<th fxHide.sm fxHide.xs mat-header-cell *matHeaderCellDef>Updated at</th>
<td fxHide.sm fxHide.xs mat-cell *matCellDef="let claim">
{{ claim.updatedAt | date: 'dd.MM.yyyy HH:mm:ss' }}
{{ claim.updated_at | date: 'dd.MM.yyyy HH:mm:ss' }}
</td>
</ng-container>
<ng-container matColumnDef="claimDetailButton">
<th mat-header-cell *matHeaderCellDef class="action-cell"></th>
<td mat-cell *matCellDef="let claim" class="action-cell">
<button mat-icon-button (click)="navigateToClaim(claim)">
<button mat-icon-button (click)="navigateToClaim(claim.party_id, claim.id)">
<mat-icon>more_vert</mat-icon>
</button>
</td>

View File

@ -1,8 +1,8 @@
import { Component, Input } from '@angular/core';
import { Router } from '@angular/router';
import { ClaimInfo } from '../../papi/model';
import { ClaimActionType } from '../../claim/claim-action-type';
import { Claim, ClaimStatus } from '../../../gen-damsel/claim_management';
import { extractClaimStatus } from '../../../shared/extract-claim-status';
@Component({
selector: 'cc-claims-table',
@ -11,10 +11,9 @@ import { ClaimActionType } from '../../claim/claim-action-type';
})
export class ClaimsTableComponent {
@Input()
claims: ClaimInfo[];
claims: Claim[];
displayedColumns = [
'partyID',
'claimID',
'status',
'revision',
@ -25,8 +24,11 @@ export class ClaimsTableComponent {
constructor(private router: Router) {}
navigateToClaim(claim: ClaimInfo) {
const c = claim as any;
this.router.navigate([`/claims/${c.partyId}/${ClaimActionType.edit}/${c.claimId}`]);
navigateToClaim(partyID: string, claimID: number) {
this.router.navigate([`party/${partyID}/claim/${claimID}`]);
}
getClaimStatus(status: ClaimStatus) {
return extractClaimStatus(status);
}
}

View File

@ -4,7 +4,7 @@
<mat-card-content>
<cc-search-form (valueChanges)="search($event)"></cc-search-form>
</mat-card-content>
<mat-card-footer *ngIf="isLoading">
<mat-card-footer *ngIf="(isLoading$ | async)">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-card-footer>
</mat-card>
@ -14,5 +14,15 @@
<cc-claim-actions></cc-claim-actions>
</mat-card-content>
</mat-card>
<div class="mat-elevation-z2"><cc-claims-table [claims]="claims"></cc-claims-table></div>
<div class="mat-elevation-z2" fxLayout="column">
<cc-claims-table [claims]="claims$ | async"></cc-claims-table>
<button
mat-raised-button
*ngIf="(hasMore$ | async)"
(click)="fetchMore()"
[disabled]="isLoading$ | async"
>
Load more...
</button>
</div>
</cc-card-container>

View File

@ -0,0 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { ClaimsService } from './claims.service';
import { SearchFormValue } from './search-form/search-form-value';
import { ClaimStatus } from '../../papi/model/claim-statuses';
@Component({
templateUrl: 'claims.component.html',
styleUrls: []
})
export class ClaimsComponent implements OnInit {
isLoading$ = this.claimService.isLoading$;
claims$ = this.claimService.claims$;
hasMore$ = this.claimService.hasMore$;
constructor(private claimService: ClaimsService) {}
ngOnInit(): void {
this.search({ statuses: [ClaimStatus.pending] });
}
search(searchFormValue: SearchFormValue) {
this.claimService.search(searchFormValue);
}
fetchMore() {
this.claimService.fetchMore();
}
}

View File

@ -18,18 +18,20 @@ import { ReactiveFormsModule } from '@angular/forms';
import { CdkTableModule } from '@angular/cdk/table';
import { ClaimsComponent } from './claims.component';
import { ClaimsRoutingModule } from './claims-routing.module';
import { PapiModule } from '../papi/papi.module';
import { PapiModule } from '../../papi/papi.module';
import { SearchFormComponent } from './search-form/search-form.component';
import { ClaimsTableComponent } from './claims-table/claims-table.component';
import { ClaimActionsComponent } from './claim-actions/claim-actions.component';
import { CreateClaimComponent } from './create-claim/create-claim.component';
import { SharedModule } from '../shared/shared.module';
import { SharedModule } from '../../shared/shared.module';
import { ClaimsService } from './claims.service';
import { ClaimManagementService } from '../../thrift/claim-management.service';
import { ClaimsRoutingModule } from './claims-routing.module';
@NgModule({
imports: [
CommonModule,
ClaimsRoutingModule,
CommonModule,
FlexLayoutModule,
PapiModule,
ReactiveFormsModule,
@ -54,6 +56,7 @@ import { SharedModule } from '../shared/shared.module';
ClaimActionsComponent,
CreateClaimComponent
],
entryComponents: [CreateClaimComponent]
entryComponents: [CreateClaimComponent],
providers: [ClaimsService, ClaimManagementService]
})
export class ClaimsModule {}

View File

@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material';
import { Observable } from 'rxjs';
import { catchError, map, shareReplay } from 'rxjs/operators';
import { FetchResult, PartialFetcher } from '@rbkmoney/partial-fetcher';
import { ClaimManagementService } from '../../thrift/claim-management.service';
import { SearchFormValue } from './search-form/search-form-value';
import { booleanDebounceTime } from '../../shared/operators';
import { convertFormValueToParams } from './convert-form-value-to-params';
import { Claim } from '../../gen-damsel/claim_management';
@Injectable()
export class ClaimsService extends PartialFetcher<Claim, SearchFormValue> {
private readonly searchLimit = 20;
claims$: Observable<Claim> = this.searchResult$.pipe(
catchError(() => {
this.snackBar.open('An error occurred while processing your search', 'OK');
return [];
})
);
isLoading$: Observable<boolean> = this.doAction$.pipe(
booleanDebounceTime(),
shareReplay(1)
);
constructor(
private claimManagementService: ClaimManagementService,
private snackBar: MatSnackBar
) {
super();
}
protected fetch(
searchFormValue: SearchFormValue,
continuationToken: string
): Observable<FetchResult<Claim>> {
return this.claimManagementService
.searchClaims({
...convertFormValueToParams(searchFormValue),
continuation_token: continuationToken,
limit: this.searchLimit
})
.pipe(
map(r => ({
result: r.result,
continuationToken: r.continuation_token
}))
);
}
}

View File

@ -0,0 +1,19 @@
import { SearchFormValue } from './search-form/search-form-value';
export const convertFormValueToParams = (params: SearchFormValue) => {
const result = {};
for (const k in params) {
if (params.hasOwnProperty(k)) {
if (k === 'statuses') {
result[k] = (params[k] as string[]).reduce((acc, cv) => [...acc, { [cv]: {} }], []);
} else {
const v = params[k].trim();
if (v === '') {
break;
}
result[k] = v;
}
}
}
return result;
};

View File

@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialogRef } from '@angular/material';
import { Router } from '@angular/router';
import { ClaimActionType } from '../../claim/claim-action-type';
import { ClaimActionType } from '../../../claim/claim-action-type';
@Component({
templateUrl: 'create-claim.component.html'

View File

@ -0,0 +1 @@
export * from './claims.module';

View File

@ -0,0 +1,7 @@
import * as domain from '../../../gen-damsel/domain';
import { ClaimStatus } from '../../../papi/model/claim-statuses';
export interface SearchFormValue {
party_id?: domain.PartyID;
statuses?: ClaimStatus[];
}

View File

@ -1,13 +1,12 @@
<form fxLayout="row" fxLayout.xs="column" fxLayoutGap="20px" [formGroup]="form">
<mat-form-field fxFlex="15" fxFlex.sm="30">
<mat-select placeholder="Claim status" formControlName="claimStatus">
<mat-option [value]="null">any</mat-option>
<mat-select placeholder="Claim status" formControlName="statuses" multiple>
<mat-option *ngFor="let status of claimStatuses" [value]="status">{{
status
}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="30" fxFlex.sm="70">
<input matInput placeholder="Party ID" formControlName="partyId" />
<input matInput placeholder="Party ID" formControlName="party_id" />
</mat-form-field>
</form>

View File

@ -2,8 +2,8 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { SearchFormService } from './search-form.service';
import { ClaimSearchParams } from '../../papi/params';
import { debounceTime } from 'rxjs/internal/operators';
import { SearchFormValue } from './search-form-value';
@Component({
selector: 'cc-search-form',
@ -12,7 +12,7 @@ import { debounceTime } from 'rxjs/internal/operators';
})
export class SearchFormComponent implements OnInit {
@Output()
valueChanges: EventEmitter<ClaimSearchParams> = new EventEmitter();
valueChanges: EventEmitter<SearchFormValue> = new EventEmitter();
form: FormGroup;
@ -21,11 +21,11 @@ export class SearchFormComponent implements OnInit {
constructor(private searchFormService: SearchFormService) {}
ngOnInit() {
const { claimStatuses, form, formValueToSearchParams } = this.searchFormService;
const { form, claimStatuses } = this.searchFormService;
this.claimStatuses = claimStatuses;
this.form = form;
this.form.valueChanges
.pipe(debounceTime(300))
.subscribe(value => this.valueChanges.emit(formValueToSearchParams(value)));
this.form.valueChanges.pipe(debounceTime(300)).subscribe(value => {
this.valueChanges.emit(value);
});
}
}

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import values from 'lodash-es/values';
import { ClaimStatus } from '../../../papi/model/claim-statuses';
@Injectable()
export class SearchFormService {
form: FormGroup;
claimStatuses: string[];
constructor(private fb: FormBuilder) {
this.form = this.prepareForm();
this.claimStatuses = values(ClaimStatus);
this.form.patchValue({ statuses: [ClaimStatus.pending] });
}
private prepareForm(): FormGroup {
return this.fb.group({
statuses: '',
party_id: ''
});
}
}

View File

@ -1,6 +1,5 @@
import { async, TestBed } from '@angular/core/testing';
import { ClaimInfoDetailsComponent } from './claim-info-details.component';
import { By } from '@angular/platform-browser';
describe('ClaimInfoDetailsComponent', () => {
beforeEach(async(() => {

View File

@ -1,39 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material';
import { HttpErrorResponse } from '@angular/common/http';
import { ClaimService } from '../papi/claim.service';
import { ClaimSearchParams } from '../papi/params';
import { ClaimInfo } from '../papi/model';
@Component({
templateUrl: 'claims.component.html',
styleUrls: []
})
export class ClaimsComponent implements OnInit {
isLoading = false;
claims: ClaimInfo[];
constructor(private claimService: ClaimService, private snackBar: MatSnackBar) {}
ngOnInit() {
this.search({ claimStatus: 'pending' });
}
search(params: ClaimSearchParams) {
this.isLoading = true;
this.claimService.getClaims(params).subscribe(
claims => {
this.isLoading = false;
this.claims = claims.reverse();
},
(error: HttpErrorResponse) => {
this.isLoading = false;
this.snackBar.open(`${error.status}: ${error.message}`, 'OK', {
duration: 1500
});
}
);
}
}

View File

@ -1,39 +0,0 @@
import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import values from 'lodash-es/values';
import mapValues from 'lodash-es/mapValues';
import isString from 'lodash-es/isString';
import { ClaimStatus } from '../../papi/model/claim-statuses';
import { ClaimSearchParams } from '../../papi/params';
@Injectable()
export class SearchFormService {
form: FormGroup;
claimStatuses: string[];
constructor(private fb: FormBuilder) {
this.form = this.prepareForm();
this.claimStatuses = values(ClaimStatus);
}
formValueToSearchParams(formValue): ClaimSearchParams {
return mapValues(formValue, value => {
let result = value;
if (value === '') {
result = null;
} else if (isString(value)) {
result = value.trim();
}
return result;
});
}
private prepareForm(): FormGroup {
return this.fb.group({
claimStatus: 'pending',
partyId: ''
});
}
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { DomainPair } from './domain-group/domain-group';
import { DomainPair } from './domain-group';
@Injectable()
export class DomainDetailsService {

View File

@ -2,12 +2,12 @@ import { Component, OnInit, Input, OnChanges, SimpleChanges, ViewChild } from '@
import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material';
import { DomainGroup } from '../domain-group';
import { DomainDetailsService } from '../../../domain-info/domain-details.service';
import { DomainDetailsService } from '../../domain-details.service';
import { toTableGroup, toDataSource } from './table-group';
import { sortData } from './sort-table-data';
import { filterPredicate } from './filter-predicate';
import { TableDataSource, TableGroup } from './model';
import { DetailsContainerService } from '../../../domain-info/details-container.service';
import { DetailsContainerService } from '../../details-container.service';
@Component({
selector: 'cc-group-table',

View File

@ -1,11 +1,10 @@
import { parse } from '../../jsonc/json-parser';
import {
CodeLensProvider,
ITextModel,
CancellationToken,
ICodeLensSymbol,
ProviderResult
} from '../../monaco-editor/model';
} from '../../monaco-editor';
export class DomainObjCodeLensProvider implements CodeLensProvider {
get language() {

View File

@ -3,7 +3,7 @@ import { MatSnackBar, MatDialog } from '@angular/material';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { MonacoFile, CodeLensProvider, CompletionProvider } from '../../monaco-editor/model';
import { MonacoFile, CodeLensProvider, CompletionProvider } from '../../monaco-editor';
import { DomainObjModificationService } from './domain-obj-modification.service';
import { DomainObjCodeLensProvider } from './domain-obj-code-lens-provider';
import { DomainObjCompletionProvider } from './domain-obj-completion-provider';

View File

@ -11,7 +11,7 @@ import { MatIconModule } from '@angular/material/icon';
import { RouterModule } from '@angular/router';
import { DomainObjModificationComponent } from './domain-obj-modification.component';
import { MonacoEditorModule } from '../../monaco-editor/monaco-editor.module';
import { MonacoEditorModule } from '../../monaco-editor';
import { SharedModule } from '../../shared/shared.module';
import { ResetConfirmDialogComponent } from './reset-confirm-dialog/reset-confirm-dialog.component';

View File

@ -3,7 +3,7 @@ import { Router } from '@angular/router';
import { MatCheckboxChange, MatSnackBar } from '@angular/material';
import { Subscription } from 'rxjs';
import { MonacoFile, IDiffEditorOptions } from '../../monaco-editor/model';
import { MonacoFile, IDiffEditorOptions } from '../../monaco-editor';
import { toMonacoFile } from '../utils';
import { DomainModificationModel } from '../domain-modification-model';
import { DomainObjReviewService } from './domain-obj-review.service';

View File

@ -10,7 +10,7 @@ import {
} from '@angular/material';
import { DomainObjReviewComponent } from './domain-obj-review.component';
import { MonacoEditorModule } from '../../monaco-editor/monaco-editor.module';
import { MonacoEditorModule } from '../../monaco-editor';
import { SharedModule } from '../../shared/shared.module';
@NgModule({

View File

@ -4,7 +4,7 @@ import { DomainRoutingModule } from './domain-routing.module';
import { DomainService } from './domain.service';
import { MetadataService } from './metadata.service';
import { DomainObjModificationModule } from './domain-obj-modification';
import { DomainInfoModule } from './domain-info/domain-info.module';
import { DomainInfoModule } from './domain-info';
import { DamselMetaModule } from '../damsel-meta/damsel-meta.module';
import { DomainObjReviewModule } from './domain-obj-review';
import { DomainReviewService } from './domain-review.service';

View File

@ -5,11 +5,12 @@ import { DepositParams } from './gen-model/fistful';
import { DepositParams as DepositParamsObject } from './gen-nodejs/fistful_types';
import { ThriftService } from '../thrift';
import * as FistfulAdmin from './gen-nodejs/FistfulAdmin';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class FistfulAdminService extends ThriftService {
constructor(zone: NgZone) {
super(zone, '/v1/admin', FistfulAdmin);
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/v1/admin', FistfulAdmin);
}
createDeposit(params: DepositParams): Observable<void> {

View File

@ -5,11 +5,12 @@ import { ThriftService } from '../thrift';
import { RepairScenario, SessionID } from './gen-model/withdrawal_session';
import { RepairScenario as RepairScenarioObject } from './gen-nodejs/withdrawal_session_types';
import * as Repairer from './gen-nodejs/Repairer';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class RepairerService extends ThriftService {
constructor(zone: NgZone) {
super(zone, '/v1/repair/withdrawal/session', Repairer);
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/v1/repair/withdrawal/session', Repairer);
}
repair = (id: SessionID, scenario: RepairScenario): Observable<void> =>

View File

@ -9,11 +9,12 @@ import {
import { ThriftService } from '../thrift';
import { Namespace } from './gen-model/base';
import { Reference, MachineDescriptor, Machine } from './gen-model/state_processing';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class AutomatonService extends ThriftService {
constructor(zone: NgZone) {
super(zone, '/v1/automaton', Automaton);
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/v1/automaton', Automaton);
}
simpleRepair = (ns: Namespace, ref: Reference): Observable<void> =>

View File

@ -6,7 +6,8 @@ import { map } from 'rxjs/operators';
import { ClaimCreated, ClaimInfo, PartyModificationUnit } from './model';
import { ConfigService } from '../core/config.service';
import { decode, encode } from '../shared/java-thrift-formatter';
import { ClaimAcceptParams, ClaimDenyParams, ClaimSearchParams } from './params';
import { ClaimAcceptParams, ClaimDenyParams } from './params';
import { ClaimSearchQuery } from '../gen-damsel/claim_management';
@Injectable()
export class ClaimService {
@ -16,7 +17,7 @@ export class ClaimService {
this.papiEndpoint = configService.config.papiEndpoint;
}
getClaims(params: ClaimSearchParams): Observable<ClaimInfo[]> {
getClaims(params: ClaimSearchQuery): Observable<ClaimInfo[]> {
return this.http.post<ClaimInfo[]>(`${this.papiEndpoint}/walk/claim/search`, params);
}

View File

@ -2,5 +2,7 @@ export enum ClaimStatus {
accepted = 'accepted',
denied = 'denied',
revoked = 'revoked',
pending = 'pending'
pending = 'pending',
review = 'review',
pending_acceptance = 'pending_acceptance'
}

View File

@ -0,0 +1,14 @@
import { ClaimStatus as UnionClaimStatus } from '../gen-damsel/claim_management';
import { ClaimStatus } from '../papi/model/claim-statuses';
export const claimStatusByUnionClaimStatus: { [name in keyof UnionClaimStatus]-?: ClaimStatus } = {
accepted: ClaimStatus.accepted,
denied: ClaimStatus.denied,
revoked: ClaimStatus.revoked,
pending: ClaimStatus.pending,
review: ClaimStatus.review,
pending_acceptance: ClaimStatus.pending_acceptance
};
export const extractClaimStatus = (status: UnionClaimStatus): ClaimStatus =>
claimStatusByUnionClaimStatus[Object.keys(status)[0] as keyof UnionClaimStatus];

View File

@ -0,0 +1,11 @@
import { distinctUntilChanged, debounce } from 'rxjs/operators';
import { timer, empty, Observable } from 'rxjs';
export const booleanDebounceTime = (timeoutMs: number = 500) => (
s: Observable<boolean>
): Observable<boolean> =>
s.pipe(
distinctUntilChanged(),
debounce(v => (v ? timer(timeoutMs) : empty())),
distinctUntilChanged()
);

View File

@ -0,0 +1 @@
export * from './boolean-debounce-time';

View File

@ -0,0 +1,18 @@
import { Injectable, NgZone } from '@angular/core';
import { Observable } from 'rxjs';
import { ThriftService } from '../thrift';
import * as ClaimManagement from './gen-nodejs/ClaimManagement';
import { ClaimSearchQuery, ClaimSearchResponse } from '../gen-damsel/claim_management';
import { ClaimSearchQuery as ClaimSearchQueryType } from './gen-nodejs/claim_management_types';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class ClaimManagementService extends ThriftService {
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/v1/cm', ClaimManagement);
}
searchClaims = (query: ClaimSearchQuery): Observable<ClaimSearchResponse> =>
this.toObservableAction('SearchClaims')(new ClaimSearchQueryType(query));
}

View File

@ -4,11 +4,12 @@ import { Observable } from 'rxjs';
import * as Repository from './gen-nodejs/Repository';
import { ThriftService } from './thrift-service';
import { Reference, Snapshot, Commit, Version, Limit } from '../gen-damsel/domain_config';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class DomainService extends ThriftService {
constructor(zone: NgZone) {
super(zone, '/v1/domain/repository', Repository);
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/v1/domain/repository', Repository);
}
checkout: (reference: Reference) => Observable<Snapshot> = this.toObservableAction('Checkout');

View File

@ -5,11 +5,12 @@ import * as MerchantStatistics from './gen-nodejs/MerchantStatistics';
import { ThriftService } from './thrift-service';
import { StatRequest, StatResponse } from '../gen-damsel/merch_stat';
import { StatRequest as ThriftStatRequest } from './gen-nodejs/merch_stat_types';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class MerchantStatisticsService extends ThriftService {
constructor(zone: NgZone) {
super(zone, '/stat', MerchantStatistics);
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/stat', MerchantStatistics);
}
getPayments = (req: StatRequest): Observable<StatResponse> =>

View File

@ -2,12 +2,7 @@ import cloneDeep from 'lodash-es/cloneDeep';
import last from 'lodash-es/last';
import dropRight from 'lodash-es/dropRight';
import {
ProviderObject,
TerminalSelector,
TerminalDecision,
TerminalRef
} from '../../gen-damsel/domain';
import { ProviderObject, TerminalDecision, TerminalRef } from '../../gen-damsel/domain';
import { toGenTerminalDecision } from '../converters';
import { checkSelector } from './utils';

View File

@ -16,11 +16,12 @@ import { ThriftService } from './thrift-service';
import * as Invoicing from './gen-nodejs/Invoicing';
import { InvoiceID } from '../gen-damsel/domain';
import { share, switchMap, first } from 'rxjs/operators';
import { KeycloakService } from 'keycloak-angular';
@Injectable()
export class PaymentProcessingService extends ThriftService {
constructor(zone: NgZone) {
super(zone, '/v1/processing/invoicing', Invoicing);
constructor(zone: NgZone, keycloakService: KeycloakService) {
super(zone, keycloakService, '/v1/processing/invoicing', Invoicing);
}
getPaymentAdjustment = (

View File

@ -1,7 +1,9 @@
import { NgZone } from '@angular/core';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';
import { Observable, from } from 'rxjs';
import { map, timeout } from 'rxjs/operators';
import connectClient from 'woody_js';
import { KeycloakService } from 'keycloak-angular';
import * as jwtDecode from 'jwt-decode';
type Exception<N = string, T = {}> = {
name: N;
@ -9,10 +11,17 @@ type Exception<N = string, T = {}> = {
} & T;
export class ThriftService {
protected realm = 'internal';
protected endpoint: string;
protected service: any;
constructor(private zone: NgZone, endpoint: string, thriftService: any) {
constructor(
private zone: NgZone,
private keycloakService: KeycloakService,
endpoint: string,
thriftService: any
) {
this.endpoint = endpoint;
this.service = thriftService;
}
@ -20,30 +29,49 @@ export class ThriftService {
protected toObservableAction<T extends (...A: any[]) => Observable<any>>(name: string): T {
return ((...args) =>
Observable.create(observer => {
const cb = msg => {
observer.error(msg);
observer.complete();
};
this.zone.run(() => {
try {
this.createClient(msg => {
observer.error(msg);
observer.complete();
})[name](...args, (ex: Exception, result) => {
ex ? observer.error(ex) : observer.next(result);
observer.complete();
});
this.createClient(cb).subscribe(client =>
client[name](...args, (ex: Exception, result) => {
ex ? observer.error(ex) : observer.next(result);
observer.complete();
})
);
} catch (e) {
observer.error(e);
observer.complete();
cb(e);
}
});
}).pipe(timeout(60000))) as any;
}
private createClient(errorCb: Function) {
return connectClient(
location.hostname,
location.port,
this.endpoint,
this.service,
errorCb
private createClient(errorCb: Function): Observable<any> {
return from(this.keycloakService.getToken()).pipe(
map(token => {
const { email, preferred_username, sub } = jwtDecode(token);
return connectClient(
location.hostname,
location.port,
this.endpoint,
this.service,
{
headers: {
'woody.meta-user-identity.email': email,
'woody.meta-user-identity.realm': 'external',
'woody.meta-user-identity.username': preferred_username,
'woody.meta-user-identity.id': sub,
'x-rbk-meta-user-identity.email': email,
'x-rbk-meta-user-identity.realm': 'external',
'x-rbk-meta-user-identity.username': preferred_username,
'x-rbk-meta-user-identity.id': sub
}
},
errorCb
);
})
);
}
}

View File

@ -5,6 +5,7 @@ import { PaymentProcessingService } from './payment-processing.service';
import { MerchantStatisticsService } from './merchant-statistics.service';
import { DomainTypedManager } from './domain-typed-manager';
import { DomainCacheService } from './domain-cache.service';
import { ClaimManagementService } from './claim-management.service';
@NgModule({
providers: [
@ -12,7 +13,8 @@ import { DomainCacheService } from './domain-cache.service';
DomainTypedManager,
PaymentProcessingService,
MerchantStatisticsService,
DomainCacheService
DomainCacheService,
ClaimManagementService
]
})
export class ThriftModule {}

View File

@ -5,7 +5,7 @@
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "es2015",
"module": "esnext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,