Improve webhook UI according to the UX

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
AllForNothing 2020-04-02 14:03:35 +08:00
parent 0319baabcb
commit ba5fd67b08
19 changed files with 88 additions and 20 deletions

View File

@ -27,7 +27,7 @@
<!-- notify type -->
<clr-select-container>
<label class="required">{{'WEBHOOK.NOTIFY_TYPE' | translate}}</label>
<select clrSelect name="notifyType" id="notify_type" [(ngModel)]="webhook.targets[0].type" [disabled]="checking">
<select class="width-238" clrSelect name="notifyType" id="notify_type" [(ngModel)]="webhook.targets[0].type" [disabled]="checking">
<option *ngFor="let type of metadata?.notify_type" value="{{type}}">{{type}}</option>
</select>
</clr-select-container>
@ -35,9 +35,9 @@
<div class="clr-form-control">
<label class="clr-control-label required">{{'WEBHOOK.EVENT_TYPE' | translate}}</label>
<div class="clr-control-container clr-control-inline" [class.clr-error]="!hasEventType()">
<div class="clr-checkbox-wrapper" *ngFor="let item of metadata?.event_type">
<div class="clr-checkbox-wrapper width-7rem" *ngFor="let item of metadata?.event_type">
<input type="checkbox" id="{{item}}" name="eventTypes" value="{{item}}" class="clr-checkbox" (change)="setEventType(item)" [checked]="getEventType(item)">
<label for="{{item}}" class="clr-control-label">{{item}}</label>
<label for="{{item}}" class="clr-control-label">{{eventTypeToText(item)}}</label>
</div>
<div class="clr-subtext-wrapper" *ngIf="!hasEventType()">
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
@ -92,12 +92,12 @@
</form>
<div class="mt-1" *ngIf="!isModify">
<button type="button" id="webhook-test-add" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid" (click)="add()">{{'BUTTON.ADD' | translate}}</button>
<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>
</div>
<div class="mt-1 bottom-btn" *ngIf="isModify">
<button type="button" [clrLoading]="checkBtnState" class="btn btn-outline" id="webhook-test" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
<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" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
</div>
</div>

View File

@ -10,4 +10,10 @@
}
.width-238 {
width: 238px;
}
.width-7rem {
width: 7rem;
}
.clr-control-label {
width: 9rem !important;
}

View File

@ -27,6 +27,9 @@ describe('AddWebhookFormComponent', () => {
},
testEndpoint() {
return of(null);
},
eventTypeToText(eventType: string) {
return eventType;
}
};
const mockMessageHandlerService = {

View File

@ -129,4 +129,7 @@ export class AddWebhookFormComponent implements OnInit {
&& this.webhook.event_types
&& this.webhook.event_types.length > 0;
}
eventTypeToText(eventType: string): string {
return this.webhookService.eventTypeToText(eventType);
}
}

View File

@ -0,0 +1,3 @@
:host::ng-deep.modal-dialog {
width: 27rem;
}

View File

@ -4,7 +4,7 @@
<clr-dg-column>{{'WEBHOOK.STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.LAST_TRIGGERED' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let item of lastTriggers">
<clr-dg-cell>{{item.event_type}}</clr-dg-cell>
<clr-dg-cell>{{eventTypeToText(item.event_type)}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="item.enabled">
<div *ngSwitchCase="true" class="icon-wrap">
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>

View File

@ -5,6 +5,8 @@ import { SharedModule } from "../../../shared/shared.module";
import { LastTriggerComponent } from "./last-trigger.component";
import { LastTrigger } from "../webhook";
import { SimpleChange } from "@angular/core";
import { of } from "rxjs";
import { WebhookService } from "../webhook.service";
describe('LastTriggerComponent', () => {
const mokedTriggers: LastTrigger[] = [
@ -23,6 +25,11 @@ describe('LastTriggerComponent', () => {
last_trigger_time: null
}
];
const mockWebhookService = {
eventTypeToText(eventType: string) {
return eventType;
}
};
let component: LastTriggerComponent;
let fixture: ComponentFixture<LastTriggerComponent>;
beforeEach(() => {
@ -35,6 +42,7 @@ describe('LastTriggerComponent', () => {
declarations: [
LastTriggerComponent
],
providers: [{ provide: WebhookService, useValue: mockWebhookService }]
});
});
beforeEach(() => {

View File

@ -3,6 +3,7 @@ import {
OnInit, SimpleChanges
} from "@angular/core";
import { LastTrigger } from "../webhook";
import { WebhookService } from "../webhook.service";
@Component({
@ -14,7 +15,7 @@ export class LastTriggerComponent implements OnInit , OnChanges {
@Input() inputLastTriggers: LastTrigger[];
@Input() webhookName: string;
lastTriggers: LastTrigger[] = [];
constructor() {
constructor(private webhookService: WebhookService) {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes && changes['inputLastTriggers']) {
@ -28,4 +29,7 @@ export class LastTriggerComponent implements OnInit , OnChanges {
}
ngOnInit(): void {
}
eventTypeToText(eventType: string): string {
return this.webhookService.eventTypeToText(eventType);
}
}

View File

@ -30,6 +30,7 @@
<clr-icon class="margin-top-0" size="16" shape="pencil"></clr-icon>
<span id="edit-webhook" class="margin-left-10">{{'BUTTON.EDIT' | translate}}</span>
</button>
<div class="dropdown-divider"></div>
<button clrDropdownItem (click)="deleteWebhook()"
class="btn btn-secondary" [disabled]="!(selectedRow && selectedRow.length >= 1)">
<clr-icon class="margin-top-0" size="16" shape="times"></clr-icon>
@ -49,9 +50,9 @@
</div>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'WEBHOOK.NAME' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
<clr-dg-column class="width-340">{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.ENABLED' | translate}}</clr-dg-column>
<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>{{'WEBHOOK.DESCRIPTION' | translate}}</clr-dg-column>
@ -60,8 +61,6 @@
</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let w of webhookList" [clrDgItem]="w">
<clr-dg-cell>{{w.name}}</clr-dg-cell>
<clr-dg-cell>{{w?.targets[0].type}}</clr-dg-cell>
<clr-dg-cell>{{w?.targets[0].address}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="w.enabled">
<div *ngSwitchCase="true" class="icon-wrap">
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
@ -72,10 +71,12 @@
<span>{{'WEBHOOK.DISABLED' | translate}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell>{{w?.targets[0].type}}</clr-dg-cell>
<clr-dg-cell>{{w?.targets[0].address}}</clr-dg-cell>
<clr-dg-cell>
<div class="cell" *ngIf="w?.event_types?.length">
<div class="bar-state">
<span class="label" *ngIf="w?.event_types[0]">{{w?.event_types[0]}}</span>
<span class="label" *ngIf="w?.event_types[0]">{{eventTypeToText(w?.event_types[0])}}</span>
</div>
<div class="signpost-item" [hidden]="w?.event_types?.length<=1">
<div class="trigger-item">
@ -84,7 +85,7 @@
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
<div>
<div *ngFor="let e of w?.event_types" class="bar-state">
<span class="label not-scan">{{e}}</span>
<span class="label not-scan">{{eventTypeToText(e)}}</span>
</div>
</div>
</clr-signpost-content>

View File

@ -55,6 +55,6 @@
justify-content: flex-end;
align-items: center;
}
.width-340 {
.min-width-340 {
min-width: 340px!important;
}

View File

@ -73,6 +73,9 @@ describe('WebhookComponent', () => {
},
editWebhook() {
return of(true);
},
eventTypeToText(eventType: string) {
return eventType;
}
};
const mockActivatedRoute = {

View File

@ -83,7 +83,13 @@ export class WebhookComponent implements OnInit {
response => {
this.metadata = response;
if (this.metadata && this.metadata.event_type) {
this.metadata.event_type.sort();
// sort by text
this.metadata.event_type.sort((a: string, b: string) => {
if (this.eventTypeToText(a) === this.eventTypeToText(b)) {
return 0;
}
return this.eventTypeToText(a) > this.eventTypeToText(b) ? 1 : -1;
});
}
},
error => {
@ -222,4 +228,7 @@ export class WebhookComponent implements OnInit {
);
this.confirmationDialogComponent.open(msg);
}
eventTypeToText(eventType: string): string {
return this.webhookService.eventTypeToText(eventType);
}
}

View File

@ -15,4 +15,11 @@ describe('WebhookService', () => {
it('should be created', inject([WebhookService], (service: WebhookService) => {
expect(service).toBeTruthy();
}));
it('function eventTypeToText should work', inject([WebhookService], (service: WebhookService) => {
expect(service).toBeTruthy();
const eventType: string = 'REPLICATION';
expect(service.eventTypeToText(eventType)).toEqual('Replication finished');
const mockedEventType: string = 'TEST';
expect(service.eventTypeToText(mockedEventType)).toEqual('TEST');
}));
});

View File

@ -18,6 +18,20 @@ import { HttpClient } from "@angular/common/http";
import { Webhook, LastTrigger } from "./webhook";
import { CURRENT_BASE_HREF } from "../../../lib/utils/utils";
const EVENT_TYPES_TEXT_MAP = {
'REPLICATION': 'Replication finished',
'PUSH_ARTIFACT': 'Artifact pushed',
'PULL_ARTIFACT': 'Artifact pulled',
'DELETE_ARTIFACT': 'Artifact deleted',
'DOWNLOAD_CHART': 'Chart downloaded',
'UPLOAD_CHART': 'Chart uploaded',
'DELETE_CHART': 'Chart deleted',
'QUOTA_EXCEED': 'Quota exceed',
'QUOTA_WARNING': 'Quota near threshold',
'SCANNING_FAILED': 'Scanning failed',
'SCANNING_COMPLETED': 'Scanning finished',
};
@Injectable()
export class WebhookService {
constructor(private http: HttpClient) { }
@ -66,4 +80,11 @@ export class WebhookService {
.get(`${CURRENT_BASE_HREF}/projects/${projectId}/webhook/events`)
.pipe(catchError(error => observableThrowError(error)));
}
public eventTypeToText(eventType: string): string {
if (EVENT_TYPES_TEXT_MAP[eventType]) {
return EVENT_TYPES_TEXT_MAP[eventType];
}
return eventType;
}
}

View File

@ -393,7 +393,7 @@
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"TARGET": "Endpoint URL",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",

View File

@ -394,7 +394,7 @@
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"TARGET": "Endpoint URL",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",

View File

@ -385,7 +385,7 @@
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"TARGET": "Endpoint URL",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",

View File

@ -423,7 +423,7 @@
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"TARGET": "Endpoint URL",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",

View File

@ -393,7 +393,7 @@
"ENABLE": "Enable",
"DISABLE": "Disable",
"NAME": "Name",
"TARGET": "Target",
"TARGET": "Endpoint URL",
"EVENT_TYPES": "Event types",
"DESCRIPTION": "Description",
"NO_WEBHOOK": "No Webhook",