METRON-1713 PCAP UI - Add a way to kill a pcap job (tiborm via merrimanr) closes apache/metron#1143
Project: http://git-wip-us.apache.org/repos/asf/metron/repo Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/14dcb2d9 Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/14dcb2d9 Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/14dcb2d9 Branch: refs/remotes/apache/feature/METRON-1699-create-batch-profiler Commit: 14dcb2d90581835d8206c65918c24e8cb04bfd06 Parents: 5b3e2c3 Author: tiborm <[email protected]> Authored: Thu Aug 9 08:33:21 2018 -0500 Committer: rmerriman <[email protected]> Committed: Thu Aug 9 08:33:21 2018 -0500 ---------------------------------------------------------------------- .../pcap/pcap-panel/pcap-panel.component.html | 7 +- .../pcap/pcap-panel/pcap-panel.component.scss | 33 +++++ .../pcap-panel/pcap-panel.component.spec.ts | 130 +++++++++++++++++++ .../app/pcap/pcap-panel/pcap-panel.component.ts | 42 +++++- .../src/app/pcap/service/pcap.service.ts | 10 +- 5 files changed, 215 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metron/blob/14dcb2d9/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.html b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.html index 950f49c..0dda268 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.html +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.html @@ -15,8 +15,11 @@ <div class="panel-header"> <app-pcap-filters [queryRunning]="queryRunning" (search)="onSearch($event)"></app-pcap-filters> </div> - <div *ngIf="queryRunning" class="progress pcap-progress-background"> - <div class="progress-bar progress-bar-animated pcap-progress" role="progressbar" attr.aria-valuenow="{{progressWidth}}" aria-valuemin="0" aria-valuemax="100" [ngStyle]="{'width': progressWidth + '%'}">{{progressWidth}}%</div> + <div *ngIf="queryRunning" class="pcap-progress-wrapper"> + <div class="progress pcap-progress-background"> + <div class="progress-bar progress-bar-animated pcap-progress" role="progressbar" attr.aria-valuenow="{{progressWidth}}" aria-valuemin="0" aria-valuemax="100" [ngStyle]="{'width': progressWidth + '%'}">{{progressWidth}}%</div> + </div> + <button data-qe-id="pcap-cancel-query-button" class="pcap-cancel-query-button btn btn-primary btn-sm" (click)="cancelQuery()" [disabled]="!queryId"></button> </div> <div *ngIf="errorMsg" class="alert alert-danger" role="alert" data-qe-id="error"> {{ errorMsg }} http://git-wip-us.apache.org/repos/asf/metron/blob/14dcb2d9/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.scss b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.scss index 8989bf9..523f5ce 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.scss +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.scss @@ -31,4 +31,37 @@ .progress-bar { width: 0; + height: 34px; + line-height: 34px; + vertical-align: middle; + font-size: 0.875rem; +} + +.pcap-progress-wrapper { + position: relative; + padding-right: 55px; +} + +.pcap-cancel-query-button { + position: absolute; + top: 0; + right: 0; + padding-top: 6px; + padding-bottom: 6px; + background: $icon-button-background; + min-width: 42px; + padding-left: 0; + padding-right: 0; + border: 1px solid $blue-chill !important; + cursor: pointer; + + &:focus { + box-shadow: none; + } + + &::before { + font-family: "FontAwesome"; + content: '\f00d'; + color: $piction-blue; + } } http://git-wip-us.apache.org/repos/asf/metron/blob/14dcb2d9/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.spec.ts b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.spec.ts index 0804b79..9dacc7f 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.spec.ts @@ -50,6 +50,9 @@ class FakePcapService { return ''; } submitRequest() {} + cancelQuery() { + return defer(() => Promise.resolve()); + } } describe('PcapPanelComponent', () => { @@ -327,6 +330,133 @@ describe('PcapPanelComponent', () => { expect(fixture.debugElement.query(By.css('app-pcap-list'))).toBeDefined(); })); + it('should render a cancel button only if a query runs', () => { + component.queryRunning = false; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]'))).toBeFalsy(); + + component.queryRunning = true; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]'))).toBeDefined(); + }); + + it('should hide the progress bar if the user clicks on the cancel button', fakeAsync(() => { + component.queryRunning = true; + component.queryId = 'testid'; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.pcap-progress'))).toBeDefined(); + + const cancelBtn = fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')); + const cancelBtnEl = cancelBtn.nativeElement; + + cancelBtnEl.click(); + tick(); + fixture.detectChanges(); + + expect(fixture.debugElement.query(By.css('.pcap-progress'))).toBeFalsy(); + })); + + it('should hide the progress bar if the cancellation request fails', fakeAsync(() => { + const restError = new RestError(); + pcapService.cancelQuery = jasmine.createSpy('cancelQuery').and.returnValue( + defer(() => Promise.reject(restError)) + ); + + component.queryRunning = true; + component.queryId = 'testid'; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.pcap-progress'))).toBeDefined(); + + const cancelBtn = fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')); + const cancelBtnEl = cancelBtn.nativeElement; + + cancelBtnEl.click(); + tick(); + fixture.detectChanges(); + + expect(fixture.debugElement.query(By.css('.pcap-progress'))).toBeFalsy(); + })); + + it('should show an error message if the cancellation request fails', fakeAsync(() => { + const restError = new RestError(); + restError.message = 'cancellation error'; + pcapService.cancelQuery = jasmine.createSpy('cancelQuery').and.returnValue( + defer(() => Promise.reject(restError)) + ); + + component.queryRunning = true; + component.queryId = 'testid'; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-qe-id="error"]'))).toBeFalsy(); + + const cancelBtn = fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')); + const cancelBtnEl = cancelBtn.nativeElement; + + cancelBtnEl.click(); + tick(); + fixture.detectChanges(); + + expect( + fixture.debugElement.query(By.css('[data-qe-id="error"]')) + .nativeElement + .textContent.trim() + ).toBe(`Response message: ${restError.message}. Something went wrong with the cancellation!`); + })); + + it('cancel button should be disabled till we get back a queryId', fakeAsync(() => { + component.queryRunning = true; + component.queryId = ''; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')).nativeElement.disabled).toBeTruthy(); + })); + + it('cancel button should be enabled when we have a queryId', fakeAsync(() => { + component.queryRunning = true; + component.queryId = 'testid'; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')).nativeElement.disabled).toBeFalsy(); + })); + + it('queryId should be emptied if the cancellation request fails', fakeAsync(() => { + const restError = new RestError(); + restError.message = 'cancellation error'; + pcapService.cancelQuery = jasmine.createSpy('cancelQuery').and.returnValue( + defer(() => Promise.reject(restError)) + ); + + component.queryRunning = true; + component.queryId = 'testid'; + fixture.detectChanges(); + + const cancelBtn = fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')); + const cancelBtnEl = cancelBtn.nativeElement; + + cancelBtnEl.click(); + tick(); + fixture.detectChanges(); + + expect(component.queryId).toBeFalsy(); + })); + + it('queryId should be emptied if the cancellation success', fakeAsync(() => { + pcapService.cancelQuery = jasmine.createSpy('cancelQuery').and.returnValue( + defer(() => Promise.resolve()) + ); + + component.queryRunning = true; + component.queryId = 'testid'; + fixture.detectChanges(); + + const cancelBtn = fixture.debugElement.query(By.css('[data-qe-id="pcap-cancel-query-button"]')); + const cancelBtnEl = cancelBtn.nativeElement; + + cancelBtnEl.click(); + tick(); + fixture.detectChanges(); + + expect(component.queryId).toBeFalsy(); + })); + it('should handle get packet 404', fakeAsync(() => { const searchResponse = new PcapStatusResponse(); searchResponse.jobId = '42'; http://git-wip-us.apache.org/repos/asf/metron/blob/14dcb2d9/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts index 8e4ced0..b11d9df 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnDestroy } from '@angular/core'; import { PcapService } from '../service/pcap.service'; import { PcapStatusResponse } from '../model/pcap-status-response'; @@ -30,13 +30,16 @@ import {RestError} from "../../model/rest-error"; templateUrl: './pcap-panel.component.html', styleUrls: ['./pcap-panel.component.scss'] }) -export class PcapPanelComponent { +export class PcapPanelComponent implements OnDestroy { @Input() pdml: Pdml = null; @Input() pcapRequest: PcapRequest; @Input() resetPaginationForSearch: boolean; statusSubscription: Subscription; + cancelSubscription: Subscription; + submitSubscription: Subscription; + getSubscription: Subscription; queryRunning: boolean = false; queryId: string; progressWidth: number = 0; @@ -55,12 +58,13 @@ export class PcapPanelComponent { onSearch(pcapRequest) { this.queryRunning = true; + this.queryId = ''; this.savedPcapRequest = pcapRequest; this.pagination.selectedPage = 1; this.pdml = null; this.progressWidth = 0; this.errorMsg = null; - this.pcapService.submitRequest(pcapRequest).subscribe((submitResponse: PcapStatusResponse) => { + this.submitSubscription = this.pcapService.submitRequest(pcapRequest).subscribe((submitResponse: PcapStatusResponse) => { let id = submitResponse.jobId; if (!id) { this.errorMsg = submitResponse.description; @@ -104,4 +108,34 @@ export class PcapPanelComponent { getDownloadUrl() { return this.pcapService.getDownloadUrl(this.queryId, this.pagination.selectedPage); } -} \ No newline at end of file + + unsubscribeAll() { + if (this.cancelSubscription) { + this.cancelSubscription.unsubscribe(); + } + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + if (this.submitSubscription) { + this.submitSubscription.unsubscribe(); + } + } + + cancelQuery() { + this.cancelSubscription = this.pcapService.cancelQuery(this.queryId) + .subscribe(() => { + this.unsubscribeAll(); + this.queryId = ''; + this.queryRunning = false; + }, (error: any) => { + this.cancelSubscription.unsubscribe(); + this.queryId = ''; + this.errorMsg = `Response message: ${error.message}. Something went wrong with the cancellation!`; + this.queryRunning = false; + }); + } + + ngOnDestroy() { + this.unsubscribeAll(); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/14dcb2d9/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.ts b/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.ts index 518cc92..85ed9cc 100644 --- a/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.ts +++ b/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.ts @@ -52,7 +52,7 @@ export class PcapService { new RequestOptions({headers: new Headers(this.defaultHeaders)})) .map(HttpUtil.extractData) .catch(HttpUtil.handleError); - } + } public getPackets(id: string, pageId: number): Observable<Pdml> { return this.http.get(`/api/v1/pcap/${id}/pdml?page=${pageId}`, new RequestOptions({headers: new Headers(this.defaultHeaders)})) .map(HttpUtil.extractData) @@ -62,4 +62,12 @@ export class PcapService { public getDownloadUrl(id: string, pageId: number) { return `/api/v1/pcap/${id}/raw?page=${pageId}`; } + + public cancelQuery(queryId: string) { + return this.http + .delete(`/api/v1/pcap/kill/${queryId}`, new RequestOptions({ + headers: new Headers(this.defaultHeaders), + })) + .catch(HttpUtil.handleError); + } } \ No newline at end of file
