Refactor webhook policy page (#14758)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Will Sun 2021-04-28 10:22:16 +08:00 committed by GitHub
parent c54e690f69
commit afa3f6d3e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 268 additions and 246 deletions

View File

@ -134,7 +134,7 @@
<span>
<button (click)="cancel()" id="system-robot-cancel" type="button" class="btn btn-outline">{{'BUTTON.CANCEL'
| translate}}</button>
<button [disabled]="disabled() || checkNameOnGoing" [clrLoading]="saveBtnState" (click)="save()" id="system-robot-save" type="button"
<button [disabled]="disabled() || checkNameOnGoing || isNameExisting" [clrLoading]="saveBtnState" (click)="save()" id="system-robot-save" type="button"
class="btn btn-primary">
<span *ngIf="isEditMode">{{'BUTTON.SAVE'| translate}}</span>
<span *ngIf="!isEditMode">{{'BUTTON.ADD'| translate}}</span>

View File

@ -233,6 +233,7 @@ export class NewRobotComponent implements OnInit, OnDestroy {
this.isEditMode = isEditMode;
this.addRobotOpened = true;
this.inlineAlertComponent.close();
this._nameSubject.next('');
}
disabled(): boolean {
if (!this.isEditMode) {

View File

@ -111,7 +111,7 @@
<span>
<button (click)="cancel()" id="system-robot-cancel" type="button" class="btn btn-outline">{{'BUTTON.CANCEL'
| translate}}</button>
<button [disabled]="disabled()|| checkNameOnGoing" [clrLoading]="saveBtnState" (click)="save()" id="system-robot-save" type="button"
<button [disabled]="disabled()|| checkNameOnGoing || isNameExisting" [clrLoading]="saveBtnState" (click)="save()" id="system-robot-save" type="button"
class="btn btn-primary">
<span *ngIf="isEditMode">{{'BUTTON.SAVE'| translate}}</span>
<span *ngIf="!isEditMode">{{'BUTTON.ADD'| translate}}</span>

View File

@ -161,6 +161,8 @@ export class AddRobotComponent implements OnInit, OnDestroy {
this.isEditMode = isEditMode;
this.addRobotOpened = true;
this.inlineAlertComponent.close();
this.isNameExisting = false;
this._nameSubject.next('');
}
disabled(): boolean {
if (!this.isEditMode) {

View File

@ -156,5 +156,5 @@ export class RuleMetadate {
}
export const RUNNING: string = "Running";
export const PENDING: string = "pending";
export const PENDING: string = "Pending";
export const TIMEOUT: number = 5000;

View File

@ -5,14 +5,16 @@
<!-- name -->
<div class="clr-form-control">
<label for="edit_endpoint_url" class="clr-control-label required">{{'WEBHOOK.NAME' | translate}}</label>
<div class="clr-control-container" [class.clr-error]="name.errors && name.errors.required && (name.dirty || name.touched)">
<div class="clr-control-container" [class.clr-error]="(name.errors && name.errors.required && (name.dirty || name.touched)) || isNameExisting">
<div class="clr-input-wrapper">
<input autocomplete="off" class="clr-input" type="text" id="name" [disabled]="checking" [(ngModel)]="webhook.name"
<input (input)="inputName()" autocomplete="off" class="clr-input" type="text" id="name" [disabled]="checking" [(ngModel)]="webhook.name"
size="30" name="notify-type" #name="ngModel" required>
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
<span class="spinner spinner-inline" [hidden]="!checkNameOnGoing"></span>
</div>
<clr-control-error *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)" class="tooltip-content">
{{'WEBHOOK.NAME_REQUIRED' | translate}}
<clr-control-error *ngIf="(name.errors && name.errors.required && (name.dirty || name.touched)) || isNameExisting" class="tooltip-content">
<span *ngIf="!(name.errors && name.errors.required && (name.dirty || name.touched)) && isNameExisting">{{'SCANNER.NAME_EXISTS' | translate}}</span>
<span *ngIf="name.errors && name.errors.required && (name.dirty || name.touched)">{{'WEBHOOK.NAME_REQUIRED' | translate}}</span>
</clr-control-error>
</div>
</div>
@ -92,10 +94,10 @@
</form>
<div class="mt-1 bottom-btn" *ngIf="!isModify">
<button type="button" class="btn btn-outline" id="add-webhook-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid" (click)="add()">{{'BUTTON.ADD' | translate}}</button>
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid || checkNameOnGoing || isNameExisting" (click)="add()">{{'BUTTON.ADD' | translate}}</button>
</div>
<div class="mt-1 bottom-btn" *ngIf="isModify">
<button type="button" class="btn btn-outline" id="edit-webhook-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid || !hasChange()" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid || !hasChange() || checkNameOnGoing || isNameExisting" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
</div>
</div>

View File

@ -1,42 +1,52 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { AddWebhookFormComponent } from './add-webhook-form.component';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { WebhookService } from "../webhook.service";
import { ProjectWebhookService } from "../webhook.service";
import { MessageHandlerService } from "../../../../shared/services/message-handler.service";
import { of } from 'rxjs';
import { Webhook } from "../webhook";
import { ErrorHandler } from '../../../../shared/units/error-handler';
import { InlineAlertComponent } from "../../../../shared/components/inline-alert/inline-alert.component";
import { WebhookPolicy } from "../../../../../../ng-swagger-gen/models/webhook-policy";
import { delay } from "rxjs/operators";
import { HttpHeaders, HttpResponse } from "@angular/common/http";
import { Registry } from "../../../../../../ng-swagger-gen/models/registry";
import { SharedTestingModule } from "../../../../shared/shared.module";
import { WebhookService } from "../../../../../../ng-swagger-gen/services/webhook.service";
describe('AddWebhookFormComponent', () => {
let component: AddWebhookFormComponent;
let fixture: ComponentFixture<AddWebhookFormComponent>;
const mockWebhookService = {
getCurrentUser: () => {
return of(null);
},
createWebhook() {
return of(null);
},
editWebhook() {
return of(null);
},
testEndpoint() {
return of(null);
},
const mockProjectWebhookService = {
eventTypeToText(eventType: string) {
return eventType;
}
};
const mockedWebhookService = {
GetSupportedEventTypes() {
return of(mockedMetadata).pipe(delay(0));
},
LastTrigger() {
return of([]).pipe(delay(0));
},
ListWebhookPoliciesOfProjectResponse() {
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
headers: new HttpHeaders({'x-total-count': [mockedWehook].length.toString()}),
body: [mockedWehook]
});
return of(response).pipe(delay(0));
},
ListWebhookPoliciesOfProject: () => {
return of(null);
},
UpdateWebhookPolicyOfProject() {
return of(true);
},
CreateWebhookPolicyOfProject() {
return of(true);
}
};
const mockMessageHandlerService = {
handleError: () => { }
};
const mockedWehook: Webhook = {
const mockedWehook: WebhookPolicy = {
id: 1,
project_id: 1,
name: 'test',
@ -44,7 +54,6 @@ describe('AddWebhookFormComponent', () => {
targets: [{
address: 'https://test.com',
type: 'http',
attachment: null,
auth_header: null,
skip_cert_verify: true,
}],
@ -77,24 +86,15 @@ describe('AddWebhookFormComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
SharedTestingModule
],
declarations: [AddWebhookFormComponent,
InlineAlertComponent,
],
providers: [
TranslateService,
{ provide: WebhookService, useValue: mockWebhookService },
{ provide: ProjectWebhookService, useValue: mockProjectWebhookService },
{ provide: WebhookService, useValue: mockedWebhookService },
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
ErrorHandler
]
})
.compileComponents();

View File

@ -4,30 +4,37 @@ import {
Input,
ViewChild,
Output,
EventEmitter,
EventEmitter, OnDestroy,
} from "@angular/core";
import { Webhook } from "../webhook";
import { NgForm } from "@angular/forms";
import { ClrLoadingState } from "@clr/angular";
import { finalize } from "rxjs/operators";
import { WebhookService } from "../webhook.service";
import { debounceTime, distinctUntilChanged, filter, finalize, switchMap } from "rxjs/operators";
import { ProjectWebhookService } from "../webhook.service";
import { compareValue } from '../../../../shared/units/utils';
import { InlineAlertComponent } from "../../../../shared/components/inline-alert/inline-alert.component";
import { WebhookService } from "../../../../../../ng-swagger-gen/services/webhook.service";
import { WebhookPolicy } from "../../../../../../ng-swagger-gen/models/webhook-policy";
import { Subject, Subscription } from "rxjs";
@Component({
selector: 'add-webhook-form',
templateUrl: './add-webhook-form.component.html',
styleUrls: ['./add-webhook-form.component.scss']
})
export class AddWebhookFormComponent implements OnInit {
export class AddWebhookFormComponent implements OnInit, OnDestroy {
closable: boolean = true;
checking: boolean = false;
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
webhookForm: NgForm;
submitting: boolean = false;
@Input() projectId: number;
webhook: Webhook = new Webhook();
originValue: Webhook;
webhook: WebhookPolicy = {
enabled: true,
event_types: [],
targets: [{
type: 'http',
address: '',
skip_cert_verify: true
}]
};
originValue: WebhookPolicy;
isModify: boolean;
@Input() isOpen: boolean;
@Output() close = new EventEmitter<boolean>();
@ -35,14 +42,62 @@ export class AddWebhookFormComponent implements OnInit {
@ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
@Input()
metadata: any;
@Output() notify = new EventEmitter<Webhook>();
@Output() notify = new EventEmitter<WebhookPolicy>();
checkNameOnGoing: boolean = false;
isNameExisting: boolean = false;
private _nameSubject = new Subject<string>();
_nameSubscription: Subscription;
constructor(
private webhookService: WebhookService,
private projectWebhookService: ProjectWebhookService,
) { }
ngOnInit() {
this.subscribeName();
}
ngOnDestroy() {
if (this._nameSubscription) {
this._nameSubscription.unsubscribe();
this._nameSubscription = null;
}
}
reset() {
this.isNameExisting = false;
this._nameSubject.next('');
}
subscribeName() {
if (!this._nameSubscription) {
this._nameSubscription = this._nameSubject
.pipe(
debounceTime(500),
distinctUntilChanged(),
filter(name => {
if (this.isModify && this.originValue && this.originValue.name === name) {
return false;
}
return name.length > 0;
}),
switchMap((name) => {
this.isNameExisting = false;
this.checkNameOnGoing = true;
return this.webhookService.ListWebhookPoliciesOfProject({
projectNameOrId: this.projectId.toString(),
q: encodeURIComponent(
`name=${name}`)
}).pipe(finalize(() => this.checkNameOnGoing = false));
}))
.subscribe(res => {
if (res && res.length > 0) {
this.isNameExisting = true;
}
});
}
}
inputName() {
this._nameSubject.next(this.webhook.name);
}
onCancel() {
this.reset();
this.close.emit(false);
this.currentForm.reset();
this.inlineAlert.close();
@ -50,10 +105,14 @@ export class AddWebhookFormComponent implements OnInit {
add() {
this.submitting = true;
this.webhookService.createWebhook(this.projectId, this.webhook)
this.webhookService.CreateWebhookPolicyOfProject({
projectNameOrId: this.projectId.toString(),
policy: this.webhook
})
.pipe(finalize(() => (this.submitting = false)))
.subscribe(
response => {
this.reset();
this.notify.emit();
this.inlineAlert.close();
},
@ -65,10 +124,15 @@ export class AddWebhookFormComponent implements OnInit {
save() {
this.submitting = true;
this.webhookService.editWebhook(this.projectId, this.webhook.id, this.webhook)
this.webhookService.UpdateWebhookPolicyOfProject({
projectNameOrId: this.projectId.toString(),
webhookPolicyId: this.webhook.id,
policy: this.webhook
})
.pipe(finalize(() => (this.submitting = false)))
.subscribe(
response => {
this.reset();
this.inlineAlert.close();
this.notify.emit();
},
@ -113,6 +177,6 @@ export class AddWebhookFormComponent implements OnInit {
&& this.webhook.event_types.length > 0;
}
eventTypeToText(eventType: string): string {
return this.webhookService.eventTypeToText(eventType);
return this.projectWebhookService.eventTypeToText(eventType);
}
}

View File

@ -1,12 +1,7 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { AddWebhookComponent } from './add-webhook.component';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { SharedTestingModule } from "../../../../shared/shared.module";
describe('AddWebhookComponent', () => {
let component: AddWebhookComponent;
let fixture: ComponentFixture<AddWebhookComponent>;
@ -18,18 +13,9 @@ describe('AddWebhookComponent', () => {
NO_ERRORS_SCHEMA
],
imports: [
BrowserAnimationsModule,
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
SharedTestingModule
],
declarations: [AddWebhookComponent],
providers: [
TranslateService
]
})
.compileComponents();
}));

View File

@ -6,8 +6,8 @@ import {
Output,
EventEmitter,
} from "@angular/core";
import { Webhook } from "../webhook";
import { AddWebhookFormComponent } from "../add-webhook-form/add-webhook-form.component";
import { WebhookPolicy } from "../../../../../../ng-swagger-gen/models/webhook-policy";
@Component({
selector: 'add-webhook',
@ -21,12 +21,12 @@ export class AddWebhookComponent implements OnInit {
staticBackdrop: boolean = true;
@Input() projectId: number;
webhook: Webhook;
webhook: WebhookPolicy;
@Input()
metadata: any;
@ViewChild(AddWebhookFormComponent)
addWebhookFormComponent: AddWebhookFormComponent;
@Output() notify = new EventEmitter<Webhook>();
@Output() notify = new EventEmitter<WebhookPolicy>();
constructor() { }

View File

@ -1,14 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { ClarityModule } from "@clr/angular";
import { SharedTestingModule } from "../../../../shared/shared.module";
import { LastTriggerComponent } from "./last-trigger.component";
import { LastTrigger } from "../webhook";
import { SimpleChange } from "@angular/core";
import { WebhookService } from "../webhook.service";
import { ProjectWebhookService } from "../webhook.service";
import { WebhookLastTrigger } from "../../../../../../ng-swagger-gen/models/webhook-last-trigger";
describe('LastTriggerComponent', () => {
const mokedTriggers: LastTrigger[] = [
const mokedTriggers: WebhookLastTrigger[] = [
{
policy_name: 'http',
enabled: true,
@ -35,13 +33,11 @@ describe('LastTriggerComponent', () => {
TestBed.configureTestingModule({
imports: [
SharedTestingModule,
BrowserAnimationsModule,
ClarityModule,
],
declarations: [
LastTriggerComponent
],
providers: [{ provide: WebhookService, useValue: mockWebhookService }]
providers: [{ provide: ProjectWebhookService, useValue: mockWebhookService }]
});
});
beforeEach(() => {

View File

@ -2,8 +2,8 @@ import {
Component, Input, OnChanges,
OnInit, SimpleChanges
} from "@angular/core";
import { LastTrigger } from "../webhook";
import { WebhookService } from "../webhook.service";
import { ProjectWebhookService } from "../webhook.service";
import { WebhookLastTrigger } from "../../../../../../ng-swagger-gen/models/webhook-last-trigger";
@Component({
@ -12,10 +12,10 @@ import { WebhookService } from "../webhook.service";
styleUrls: ['./last-trigger.component.scss']
})
export class LastTriggerComponent implements OnInit , OnChanges {
@Input() inputLastTriggers: LastTrigger[];
@Input() inputLastTriggers: WebhookLastTrigger[];
@Input() webhookName: string;
lastTriggers: LastTrigger[] = [];
constructor(private webhookService: WebhookService) {
lastTriggers: WebhookLastTrigger[] = [];
constructor(private webhookService: ProjectWebhookService) {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes && changes['inputLastTriggers']) {

View File

@ -1,10 +1,10 @@
<div class="row">
<h4 class="mt-1">{{'WEBHOOK.WEBHOOKS' | translate}}</h4>
<clr-datagrid [clrDgLoading]="loadingWebhookList || loadingMetadata" [(clrDgSelected)]="selectedRow">
<clr-datagrid (clrDgRefresh)="getWebhooks($event)" [clrDgLoading]="loadingWebhookList" [(clrDgSelected)]="selectedRow">
<clr-dg-action-bar>
<div class="clr-row">
<div class="clr-col-7">
<button [disabled]="!hasCreatPermission" [clrLoading]="addBtnState" id="new-webhook" type="button" class="btn btn-secondary" (click)="newWebhook()">
<button [disabled]="loadingMetadata||!hasCreatPermission" [clrLoading]="loadingMetadata||addBtnState" id="new-webhook" type="button" class="btn btn-secondary" (click)="newWebhook()">
<clr-icon shape="plus" size="16"></clr-icon>
{{'WEBHOOK.NEW_WEBHOOK' | translate}}
</button>
@ -43,7 +43,7 @@
<div class="clr-col-5">
<div class="action-head-pos">
<span class="refresh-btn">
<clr-icon shape="refresh" (click)="success()" [hidden]="loadingWebhookList || loadingMetadata"></clr-icon>
<clr-icon shape="refresh" (click)="refresh()" [hidden]="loadingWebhookList || loadingMetadata"></clr-icon>
</span>
</div>
</div>
@ -54,12 +54,12 @@
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
<clr-dg-column class="min-width-340">{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.EVENT_TYPES' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'creation_time'">{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.DESCRIPTION' | translate}}</clr-dg-column>
<clr-dg-placeholder>
{{'WEBHOOK.NO_WEBHOOK' | translate}}
</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let w of webhookList" [clrDgItem]="w">
<clr-dg-row *ngFor="let w of webhookList" [clrDgItem]="w">
<clr-dg-cell>{{w.name}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="w.enabled">
<div *ngSwitchCase="true" class="icon-wrap">
@ -101,9 +101,10 @@
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination [clrDgPageSize]="15">
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="page" [clrDgTotalItems]="total">
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
<span *ngIf="webhookList?.length > 0">1 - {{webhookList?.length}} {{'WEBHOOK.OF' | translate}} </span> {{webhookList?.length}} {{'WEBHOOK.ITEMS' | translate}}
<span *ngIf="total">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'WEBHOOK.OF' | translate}}</span>
{{total}} {{'WEBHOOK.ITEMS' | translate}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -1,18 +1,21 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { WebhookComponent } from './webhook.component';
import { ActivatedRoute } from '@angular/router';
import { WebhookService } from './webhook.service';
import { ProjectWebhookService } from './webhook.service';
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
import { of } from 'rxjs';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { delay } from "rxjs/operators";
import { Webhook } from "./webhook";
import { AddWebhookFormComponent } from "./add-webhook-form/add-webhook-form.component";
import { AddWebhookComponent } from "./add-webhook/add-webhook.component";
import { ConfirmationDialogComponent } from "../../../shared/components/confirmation-dialog";
import { UserPermissionService } from '../../../shared/services';
import { InlineAlertComponent } from "../../../shared/components/inline-alert/inline-alert.component";
import { SharedTestingModule } from "../../../shared/shared.module";
import { WebhookPolicy } from "../../../../../ng-swagger-gen/models/webhook-policy";
import { WebhookService } from "../../../../../ng-swagger-gen/services/webhook.service";
import { HttpHeaders, HttpResponse } from "@angular/common/http";
import { Registry } from "../../../../../ng-swagger-gen/models/registry";
describe('WebhookComponent', () => {
let component: WebhookComponent;
@ -37,7 +40,7 @@ describe('WebhookComponent', () => {
"slack"
]
};
const mockedWehook: Webhook = {
const mockedWehook: WebhookPolicy = {
id: 1,
project_id: 1,
name: 'test',
@ -45,7 +48,6 @@ describe('WebhookComponent', () => {
targets: [{
address: 'https://test.com',
type: 'http',
attachment: null,
auth_header: null,
skip_cert_verify: true,
}],
@ -57,24 +59,29 @@ describe('WebhookComponent', () => {
update_time: null,
enabled: true,
};
const mockWebhookService = {
listLastTrigger: () => {
return of([]).pipe(delay(0));
},
listWebhook: () => {
return of([mockedWehook
]).pipe(delay(0));
},
getWebhookMetadata() {
return of(mockedMetadata).pipe(delay(0));
},
editWebhook() {
return of(true);
},
const mockProjectWebhookService = {
eventTypeToText(eventType: string) {
return eventType;
}
};
const mockedWebhookService = {
GetSupportedEventTypes() {
return of(mockedMetadata).pipe(delay(0));
},
LastTrigger() {
return of([]).pipe(delay(0));
},
ListWebhookPoliciesOfProjectResponse() {
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
headers: new HttpHeaders({'x-total-count': [mockedWehook].length.toString()}),
body: [mockedWehook]
});
return of(response).pipe(delay(0));
},
UpdateWebhookPolicyOfProject() {
return of(true);
}
};
const mockActivatedRoute = {
RouterparamMap: of({ get: (key) => 'value' }),
snapshot: {
@ -113,7 +120,8 @@ describe('WebhookComponent', () => {
ConfirmationDialogComponent
],
providers: [
{ provide: WebhookService, useValue: mockWebhookService },
{ provide: ProjectWebhookService, useValue: mockProjectWebhookService },
{ provide: WebhookService, useValue: mockedWebhookService },
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: UserPermissionService, useValue: mockUserPermissionService },

View File

@ -17,17 +17,19 @@ import { Component, OnInit, ViewChild } from '@angular/core';
import { AddWebhookComponent } from './add-webhook/add-webhook.component';
import { AddWebhookFormComponent } from './add-webhook-form/add-webhook-form.component';
import { ActivatedRoute } from '@angular/router';
import { LastTrigger, Webhook } from './webhook';
import { WebhookService } from './webhook.service';
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
import { Project } from '../project';
import { clone } from '../../../shared/units/utils';
import { clone, DEFAULT_PAGE_SIZE, getSortingString } from '../../../shared/units/utils';
import { forkJoin, Observable } from 'rxjs';
import { UserPermissionService, USERSTATICPERMISSION } from '../../../shared/services';
import { ClrLoadingState } from '@clr/angular';
import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular';
import { ConfirmationDialogComponent } from "../../../shared/components/confirmation-dialog";
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
import { WebhookService } from "../../../../../ng-swagger-gen/services/webhook.service";
import { WebhookLastTrigger } from "../../../../../ng-swagger-gen/models/webhook-last-trigger";
import { WebhookPolicy } from "../../../../../ng-swagger-gen/models/webhook-policy";
import { ProjectWebhookService } from "./webhook.service";
@Component({
templateUrl: './webhook.component.html',
@ -40,23 +42,27 @@ export class WebhookComponent implements OnInit {
addWebhookFormComponent: AddWebhookFormComponent;
@ViewChild("confirmationDialogComponent")
confirmationDialogComponent: ConfirmationDialogComponent;
lastTriggers: LastTrigger[] = [];
lastTriggerCount: number = 0;
lastTriggers: WebhookLastTrigger[] = [];
projectId: number;
projectName: string;
selectedRow: Webhook[] = [];
webhookList: Webhook[] = [];
selectedRow: WebhookPolicy[] = [];
webhookList: WebhookPolicy[] = [];
metadata: any;
loadingMetadata: boolean = false;
loadingWebhookList: boolean = false;
loadingMetadata: boolean = true;
loadingWebhookList: boolean = true;
loadingTriggers: boolean = false;
hasCreatPermission: boolean = false;
hasUpdatePermission: boolean = false;
addBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
page: number = 1;
pageSize: number = DEFAULT_PAGE_SIZE;
total: number = 0;
state: ClrDatagridStateInterface;
constructor(
private route: ActivatedRoute,
private translate: TranslateService,
private webhookService: WebhookService,
private projectWebhookService: ProjectWebhookService,
private messageHandlerService: MessageHandlerService,
private userPermissionService: UserPermissionService) { }
@ -85,16 +91,23 @@ export class WebhookComponent implements OnInit {
this.addBtnState = ClrLoadingState.ERROR;
});
}
refresh() {
this.page = 1;
this.total = 0;
this.selectedRow = [];
this.getWebhooks(this.state);
this.getData();
}
getData() {
this.getMetadata();
this.getLastTriggers();
this.getWebhooks();
this.selectedRow = [];
}
getMetadata() {
this.loadingMetadata = true;
this.webhookService.getWebhookMetadata(this.projectId)
this.webhookService.GetSupportedEventTypes({
projectNameOrId: this.projectId.toString()
})
.pipe(finalize(() => (this.loadingMetadata = false)))
.subscribe(
response => {
@ -118,12 +131,13 @@ export class WebhookComponent implements OnInit {
getLastTriggers() {
this.loadingTriggers = true;
this.webhookService
.listLastTrigger(this.projectId)
.LastTrigger({
projectNameOrId: this.projectId.toString()
})
.pipe(finalize(() => (this.loadingTriggers = false)))
.subscribe(
response => {
this.lastTriggers = response;
this.lastTriggerCount = response.length;
},
error => {
this.messageHandlerService.handleError(error);
@ -131,14 +145,43 @@ export class WebhookComponent implements OnInit {
);
}
getWebhooks() {
getWebhooks(state?: ClrDatagridStateInterface) {
if (state) {
this.state = state;
}
if (state && state.page) {
this.pageSize = state.page.size;
}
let q: string;
if (state && state.filters && state.filters.length) {
q = encodeURIComponent(`${state.filters[0].property}=~${state.filters[0].value}`);
}
let sort: string;
if (state && state.sort && state.sort.by) {
sort = getSortingString(state);
} else { // sort by creation_time desc by default
sort = `-creation_time`;
}
this.loadingWebhookList = true;
this.webhookService
.listWebhook(this.projectId)
.ListWebhookPoliciesOfProjectResponse({
projectNameOrId: this.projectId.toString(),
page: this.page,
pageSize: this.pageSize,
sort: sort,
q: q
})
.pipe(finalize(() => (this.loadingWebhookList = false)))
.subscribe(
response => {
this.webhookList = response;
// Get total count
if (response.headers) {
let xHeader: string = response.headers.get("X-Total-Count");
if (xHeader) {
this.total = parseInt(xHeader, 0);
}
}
this.webhookList = response.body || [];
},
error => {
this.messageHandlerService.handleError(error);
@ -172,11 +215,14 @@ export class WebhookComponent implements OnInit {
message.state === ConfirmationState.CONFIRMED) {
if (JSON.stringify(message.data) === '{}') {
this.webhookService
.editWebhook(this.projectId, this.selectedRow[0].id,
Object.assign({}, this.selectedRow[0], { enabled: !this.selectedRow[0].enabled }))
.UpdateWebhookPolicyOfProject({
projectNameOrId: this.projectId.toString(),
webhookPolicyId: this.selectedRow[0].id,
policy: Object.assign({}, this.selectedRow[0], { enabled: !this.selectedRow[0].enabled })
})
.subscribe(
response => {
this.getData();
this.refresh();
},
error => {
this.messageHandlerService.handleError(error);
@ -185,11 +231,14 @@ export class WebhookComponent implements OnInit {
} else {
const observableLists: Observable<any>[] = [];
message.data.forEach(item => {
observableLists.push(this.webhookService.deleteWebhook(this.projectId, item.id));
observableLists.push(this.webhookService.DeleteWebhookPolicyOfProject({
projectNameOrId: this.projectId.toString(),
webhookPolicyId: item.id
}));
});
forkJoin(...observableLists).subscribe(
response => {
this.getData();
this.refresh();
},
error => {
this.messageHandlerService.handleError(error);
@ -219,12 +268,20 @@ export class WebhookComponent implements OnInit {
this.addWebhookComponent.isEdit = false;
this.addWebhookComponent.addWebhookFormComponent.isModify = false;
this.addWebhookComponent.addWebhookFormComponent.currentForm.reset({notifyType: this.metadata.notify_type[0]});
this.addWebhookComponent.addWebhookFormComponent.webhook = new Webhook();
this.addWebhookComponent.addWebhookFormComponent.webhook = {
enabled: true,
event_types: [],
targets: [{
type: 'http',
address: '',
skip_cert_verify: true
}]
};
this.addWebhookComponent.addWebhookFormComponent.webhook.event_types = clone(this.metadata.event_type);
}
}
success() {
this.getData();
this.refresh();
}
deleteWebhook() {
@ -247,6 +304,6 @@ export class WebhookComponent implements OnInit {
this.confirmationDialogComponent.open(msg);
}
eventTypeToText(eventType: string): string {
return this.webhookService.eventTypeToText(eventType);
return this.projectWebhookService.eventTypeToText(eventType);
}
}

View File

@ -5,7 +5,7 @@ import { WebhookComponent } from "./webhook.component";
import { LastTriggerComponent } from "./last-trigger/last-trigger.component";
import { AddWebhookFormComponent } from "./add-webhook-form/add-webhook-form.component";
import { AddWebhookComponent } from "./add-webhook/add-webhook.component";
import { WebhookService } from "./webhook.service";
import { ProjectWebhookService } from "./webhook.service";
const routes: Routes = [
@ -26,7 +26,7 @@ const routes: Routes = [
SharedModule
],
providers: [
WebhookService
ProjectWebhookService
]
})
export class WebhookModule { }

View File

@ -1,21 +1,17 @@
import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { WebhookService } from './webhook.service';
import { ProjectWebhookService } from './webhook.service';
describe('WebhookService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [WebhookService]
providers: [ProjectWebhookService]
});
});
it('should be created', inject([WebhookService], (service: WebhookService) => {
it('should be created', inject([ProjectWebhookService], (service: ProjectWebhookService) => {
expect(service).toBeTruthy();
}));
it('function eventTypeToText should work', inject([WebhookService], (service: WebhookService) => {
it('function eventTypeToText should work', inject([ProjectWebhookService], (service: ProjectWebhookService) => {
expect(service).toBeTruthy();
const eventType: string = 'REPLICATION';
expect(service.eventTypeToText(eventType)).toEqual('Replication finished');

View File

@ -11,12 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { throwError as observableThrowError, Observable, of } from "rxjs";
import { map, catchError, delay } from "rxjs/operators";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Webhook, LastTrigger } from "./webhook";
import { CURRENT_BASE_HREF } from "../../../shared/units/utils";
const EVENT_TYPES_TEXT_MAP = {
'REPLICATION': 'Replication finished',
@ -34,54 +29,8 @@ const EVENT_TYPES_TEXT_MAP = {
};
@Injectable()
export class WebhookService {
constructor(private http: HttpClient) { }
public listWebhook(projectId: number): Observable<Webhook[]> {
return this.http
.get(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies`)
.pipe(map(response => response as Webhook[]))
.pipe(catchError(error => observableThrowError(error)));
}
public listLastTrigger(projectId: number): Observable<LastTrigger[]> {
return this.http
.get(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/lasttrigger`)
.pipe(map(response => response as LastTrigger[]))
.pipe(catchError(error => observableThrowError(error)));
}
public editWebhook(projectId: number, policyId: number, data: any): Observable<any> {
return this.http
.put(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies/${policyId}`, data)
.pipe(catchError(error => observableThrowError(error)));
}
public deleteWebhook(projectId: number, policyId: number): Observable<any> {
return this.http
.delete(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies/${policyId}`)
.pipe(catchError(error => observableThrowError(error)));
}
public createWebhook(projectId: number, data: any): Observable<any> {
return this.http
.post(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies`, data)
.pipe(catchError(error => observableThrowError(error)));
}
public testEndpoint(projectId: number, param): Observable<any> {
return this.http
.post(`${ CURRENT_BASE_HREF }/projects/${projectId}/webhook/policies/test`, param)
.pipe(catchError(error => observableThrowError(error)));
}
public getWebhookMetadata(projectId: number): Observable<any> {
return this.http
.get(`${CURRENT_BASE_HREF}/projects/${projectId}/webhook/events`)
.pipe(catchError(error => observableThrowError(error)));
}
export class ProjectWebhookService {
constructor() { }
public eventTypeToText(eventType: string): string {
if (EVENT_TYPES_TEXT_MAP[eventType]) {
return EVENT_TYPES_TEXT_MAP[eventType];

View File

@ -1,40 +0,0 @@
export class Webhook {
id: number;
name: string;
project_id: number;
description: string;
targets: Target[];
event_types: string[];
creator: string;
creation_time: Date;
update_time: Date;
enabled: boolean;
constructor () {
this.targets = [];
this.targets.push(new Target());
this.event_types = [];
this.enabled = true;
}
}
export class Target {
type: string;
address: string;
attachment: string;
auth_header: string;
skip_cert_verify: boolean;
constructor () {
this.type = 'http';
this.address = '';
this.skip_cert_verify = true;
}
}
export class LastTrigger {
policy_name: string;
enabled: boolean;
event_type: string;
creation_time: Date;
last_trigger_time: Date;
}