mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-14 22:00:30 +09:00
#103 fixed log retrieval through websocket
This commit is contained in:
@@ -5,12 +5,16 @@ import {jest} from '@jest/globals';
|
||||
|
||||
describe('LogsPanelComponent', () => {
|
||||
|
||||
const ngZone = {run: jest.fn((fn: () => void) => fn())};
|
||||
|
||||
beforeEach(() => ngZone.run.mockClear());
|
||||
|
||||
it('should subscribe to the selected trigger logs topic', () => {
|
||||
const messages = new Subject<any>();
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
|
||||
@@ -26,6 +30,7 @@ describe('LogsPanelComponent', () => {
|
||||
};
|
||||
messages.next({body: JSON.stringify(logRecord)});
|
||||
|
||||
expect(ngZone.run).toHaveBeenCalled();
|
||||
expect(component.logs[0]).toEqual({
|
||||
time: logRecord.date.toISOString(),
|
||||
type: 'INFO',
|
||||
@@ -43,7 +48,7 @@ describe('LogsPanelComponent', () => {
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
const firstSubscription = component.topicSubscription;
|
||||
@@ -64,7 +69,7 @@ describe('LogsPanelComponent', () => {
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
firstMessages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})});
|
||||
@@ -89,7 +94,7 @@ describe('LogsPanelComponent', () => {
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
messages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})});
|
||||
@@ -105,7 +110,7 @@ describe('LogsPanelComponent', () => {
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null, ngZone as any);
|
||||
|
||||
expect(() => component.ngOnDestroy()).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, Input, NgZone, OnDestroy, OnInit} from '@angular/core';
|
||||
|
||||
import {ApiService} from '../../services';
|
||||
import {LogsRxWebsocketService} from '../../services/logs.rx-websocket.service';
|
||||
@@ -23,11 +23,12 @@ export class LogsPanelComponent implements OnInit, OnDestroy {
|
||||
|
||||
private selectedTriggerKey: TriggerKey;
|
||||
|
||||
constructor(
|
||||
private logsRxWebsocketService: LogsRxWebsocketService,
|
||||
private apiService: ApiService
|
||||
) {
|
||||
}
|
||||
constructor(
|
||||
private logsRxWebsocketService: LogsRxWebsocketService,
|
||||
private apiService: ApiService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
}
|
||||
|
||||
@Input()
|
||||
set triggerKey(triggerKey: TriggerKey) {
|
||||
@@ -58,8 +59,8 @@ export class LogsPanelComponent implements OnInit, OnDestroy {
|
||||
this._unsubscribeFromTopic();
|
||||
this.topicSubscription = this.logsRxWebsocketService.watch(`/topic/logs/${triggerKey.name}`)
|
||||
.pipe(map((msg: any) => JSON.parse(msg.body)))
|
||||
.subscribe(this._showNewLog, (err) => {
|
||||
console.log(err);
|
||||
.subscribe(logRecord => this.ngZone.run(() => this._showNewLog(logRecord)), (err) => {
|
||||
console.log(err);
|
||||
// TODO in case of 401
|
||||
// this.apiService.get('/quartz-manager/session/refresh');
|
||||
});
|
||||
|
||||
@@ -5,13 +5,17 @@ import {jest} from '@jest/globals';
|
||||
|
||||
describe('ProgressPanelComponent', () => {
|
||||
|
||||
const ngZone = {run: jest.fn((fn: () => void) => fn())};
|
||||
|
||||
beforeEach(() => ngZone.run.mockClear());
|
||||
|
||||
it('should subscribe to the selected trigger progress topic', () => {
|
||||
jest.useFakeTimers();
|
||||
const messages = new Subject<any>();
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
|
||||
@@ -20,6 +24,7 @@ describe('ProgressPanelComponent', () => {
|
||||
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(ngZone.run).toHaveBeenCalled();
|
||||
expect(component.progress.percentage).toEqual(75);
|
||||
expect(component.percentageStr).toEqual('75%');
|
||||
expect(component.progressUpdated).toBeTruthy();
|
||||
@@ -34,7 +39,7 @@ describe('ProgressPanelComponent', () => {
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
const firstSubscription = component.topicSubscription;
|
||||
@@ -55,7 +60,7 @@ describe('ProgressPanelComponent', () => {
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
firstMessages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
|
||||
@@ -78,7 +83,7 @@ describe('ProgressPanelComponent', () => {
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
|
||||
@@ -94,7 +99,7 @@ describe('ProgressPanelComponent', () => {
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any, ngZone as any);
|
||||
|
||||
expect(() => component.ngOnDestroy()).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core'
|
||||
import {Component, Input, NgZone, OnDestroy, OnInit} from '@angular/core'
|
||||
import TriggerFiredBundle from '../../model/trigger-fired-bundle.model';
|
||||
import {TriggerKey} from '../../model/triggerKey.model';
|
||||
import {ProgressRxWebsocketService} from '../../services/progress.rx-websocket.service';
|
||||
@@ -18,9 +18,10 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
|
||||
topicSubscription;
|
||||
private selectedTriggerKey: TriggerKey;
|
||||
|
||||
constructor(
|
||||
private progressRxWebsocketService: ProgressRxWebsocketService
|
||||
) { }
|
||||
constructor(
|
||||
private progressRxWebsocketService: ProgressRxWebsocketService,
|
||||
private ngZone: NgZone
|
||||
) { }
|
||||
|
||||
@Input()
|
||||
set triggerKey(triggerKey: TriggerKey) {
|
||||
@@ -44,8 +45,8 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
|
||||
this._unsubscribeFromTopic();
|
||||
this.topicSubscription = this.progressRxWebsocketService.watch(`/topic/progress/${triggerKey.name}`)
|
||||
.pipe(map((msg: any) => JSON.parse(msg.body)))
|
||||
.subscribe(this.onNewProgressMsg, (err) => {
|
||||
console.log(err);
|
||||
.subscribe(progress => this.ngZone.run(() => this.onNewProgressMsg(progress)), (err) => {
|
||||
console.log(err);
|
||||
// TODO in case of 401
|
||||
// this.apiService.get('/quartz-manager/session/refresh');
|
||||
});
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
</div>
|
||||
<div fxFlex="1 1 auto" style="text-align: center" *ngIf="!simpleTriggerReactiveForm.enabled">
|
||||
<button mat-raised-button type="button"
|
||||
(click)="simpleTriggerReactiveForm.enable();simpleTriggerReactiveForm.controls['triggerName'].disable();">
|
||||
(click)="openTriggerForm();simpleTriggerReactiveForm.controls['triggerName'].disable();">
|
||||
Reschedule
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, fakeAsync, flush, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {SimpleTriggerConfigComponent} from './simple-trigger-config.component';
|
||||
import {ApiService, ConfigService, CONTEXT_PATH, SchedulerService} from '../../services';
|
||||
@@ -124,7 +124,7 @@ describe('SimpleTriggerConfig', () => {
|
||||
openFormAndFillAllMandatoryFields();
|
||||
});
|
||||
|
||||
it('should emit an event when a new trigger is submitted', () => {
|
||||
it('should emit an event when a new trigger is submitted', fakeAsync(() => {
|
||||
const componentDe: DebugElement = fixture.debugElement;
|
||||
const mockTrigger = new Trigger();
|
||||
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
|
||||
@@ -143,14 +143,18 @@ describe('SimpleTriggerConfig', () => {
|
||||
|
||||
let actualNewTrigger;
|
||||
component.onNewTrigger.subscribe(simpleTrigger => actualNewTrigger = simpleTrigger);
|
||||
let submittedTriggerKey: TriggerKey;
|
||||
component.onTriggerSubmitting.subscribe(triggerKey => submittedTriggerKey = triggerKey);
|
||||
|
||||
submitButton.nativeElement.click();
|
||||
expect(submittedTriggerKey).toEqual(new TriggerKey(testTriggerName, null));
|
||||
flush();
|
||||
|
||||
const postSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
|
||||
postSimpleTriggerReq.flush(mockTrigger);
|
||||
|
||||
expect(actualNewTrigger).toEqual(mockTrigger);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not emit an event when an existing trigger is edited', () => {
|
||||
const mockTriggerKey = new TriggerKey(testTriggerName, null);
|
||||
@@ -247,7 +251,7 @@ describe('SimpleTriggerConfig', () => {
|
||||
|
||||
expect(component.simpleTriggerReactiveForm.value.triggerName).toEqual(testTriggerName);
|
||||
|
||||
component.triggerKey = null;
|
||||
component.openNewTriggerForm();
|
||||
|
||||
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
|
||||
expect(component.simpleTriggerReactiveForm.value.jobClass).toBeNull();
|
||||
@@ -255,6 +259,16 @@ describe('SimpleTriggerConfig', () => {
|
||||
|
||||
});
|
||||
|
||||
it('should not emit form open changes while applying a null trigger input', () => {
|
||||
let formOpenChangeEmitted = false;
|
||||
component.triggerFormOpenChange.subscribe(() => formOpenChangeEmitted = true);
|
||||
|
||||
component.triggerKey = null;
|
||||
|
||||
expect(formOpenChangeEmitted).toBeFalsy();
|
||||
expect(component.shouldShowTheTriggerCardContent()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should display the warning if there are no eligible jobs', () => {
|
||||
fixture.detectChanges();
|
||||
const getJobsReq = httpTestingController.expectOne(`${CONTEXT_PATH}/jobs`);
|
||||
|
||||
@@ -42,14 +42,15 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
|
||||
private jobs: Array<String>;
|
||||
|
||||
enabledTriggerForm = false;
|
||||
|
||||
@Output()
|
||||
onNewTrigger = new EventEmitter<SimpleTrigger>();
|
||||
|
||||
@Output()
|
||||
triggerFormOpenChange = new EventEmitter<boolean>();
|
||||
|
||||
@Output()
|
||||
onTriggerSubmitting = new EventEmitter<TriggerKey>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private schedulerService: SchedulerService,
|
||||
@@ -58,36 +59,33 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.simpleTriggerReactiveForm.disable();
|
||||
this.fetchJobs();
|
||||
}
|
||||
|
||||
openTriggerForm() {
|
||||
this.simpleTriggerReactiveForm.enable();
|
||||
}
|
||||
|
||||
private fetchJobs() {
|
||||
this.jobService.fetchJobs().subscribe(jobs => this.jobs = jobs);
|
||||
}
|
||||
|
||||
openTriggerForm() {
|
||||
this.enabledTriggerForm = true;
|
||||
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
|
||||
this.simpleTriggerReactiveForm.enable();
|
||||
this.triggerFormOpenChange.emit(true);
|
||||
}
|
||||
|
||||
private closeTriggerForm() {
|
||||
this.enabledTriggerForm = false;
|
||||
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
|
||||
this.simpleTriggerReactiveForm.disable();
|
||||
this.triggerFormOpenChange.emit(false);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set triggerKey(triggerKey: TriggerKey) {
|
||||
if (!triggerKey) {
|
||||
this.openNewTriggerForm();
|
||||
return;
|
||||
} else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) {
|
||||
this._resetTheTrigger();
|
||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||
this.fetchSelectedTrigger();
|
||||
this.closeTriggerForm();
|
||||
this.simpleTriggerReactiveForm.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +131,17 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
this.schedulerService.updateSimpleTriggerConfig : this.schedulerService.saveSimpleTriggerConfig;
|
||||
|
||||
const simpleTriggerCommand = this._fromReactiveFormToCommand();
|
||||
if (!this.trigger) {
|
||||
this.onTriggerSubmitting.emit(new TriggerKey(simpleTriggerCommand.triggerName, null));
|
||||
setTimeout(() => this.submitTriggerConfig(schedulerServiceCall, simpleTriggerCommand));
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitTriggerConfig(schedulerServiceCall, simpleTriggerCommand);
|
||||
|
||||
}
|
||||
|
||||
private submitTriggerConfig(schedulerServiceCall, simpleTriggerCommand: SimpleTriggerCommand) {
|
||||
this.triggerLoading = true;
|
||||
schedulerServiceCall(simpleTriggerCommand)
|
||||
.subscribe((retTrigger: SimpleTrigger) => {
|
||||
@@ -153,9 +162,8 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
}
|
||||
this.triggerLoading = false;
|
||||
}, () => {
|
||||
this.triggerLoading = false
|
||||
});
|
||||
|
||||
this.triggerLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
private _triggerPeriodValidator(control: AbstractControl): ValidationErrors | null {
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
<div fxLayout="row" fxFill>
|
||||
<div fxLayout="column" fxFill>
|
||||
<qrzmng-simple-trigger-config
|
||||
fxFill
|
||||
[triggerKey]="selectedTriggerKey"
|
||||
(triggerFormOpenChange)="setNewTriggerFormOpened($event)"
|
||||
(onNewTrigger)="onNewTriggerCreated($event)">
|
||||
</qrzmng-simple-trigger-config>
|
||||
fxFill
|
||||
[triggerKey]="selectedTriggerKey"
|
||||
(triggerFormOpenChange)="setNewTriggerFormOpened($event)"
|
||||
(onTriggerSubmitting)="monitorTrigger($event)"
|
||||
(onNewTrigger)="onNewTriggerCreated($event)">
|
||||
</qrzmng-simple-trigger-config>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,16 +31,16 @@
|
||||
<div class="flex-1">
|
||||
<div class="h-100 min-h-100 flex flex-column gap-6">
|
||||
<div class="flex flex-column" >
|
||||
<progress-panel class="flex-1"
|
||||
[triggerKey]=selectedTriggerKey
|
||||
>
|
||||
</progress-panel>
|
||||
<progress-panel class="flex-1"
|
||||
[triggerKey]=monitoredTriggerKey
|
||||
>
|
||||
</progress-panel>
|
||||
</div>
|
||||
<div class="flex flex-column flex-1" style="max-height: calc(100% - 136px); min-height: calc(100% - 210px);">
|
||||
<logs-panel class="flex flex-1 h-100 max-h-100"
|
||||
[triggerKey]=selectedTriggerKey
|
||||
>
|
||||
</logs-panel>
|
||||
<logs-panel class="flex flex-1 h-100 max-h-100"
|
||||
[triggerKey]=monitoredTriggerKey
|
||||
>
|
||||
</logs-panel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,12 +20,15 @@ export class ManagerComponent implements OnInit {
|
||||
|
||||
selectedTriggerKey: TriggerKey;
|
||||
|
||||
monitoredTriggerKey: TriggerKey;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onNewTriggerRequested() {
|
||||
this.selectedTriggerKey = null;
|
||||
this.monitoredTriggerKey = null;
|
||||
this.newTriggerFormOpened = true;
|
||||
if (this.triggerConfigComponent) {
|
||||
this.triggerConfigComponent.openNewTriggerForm();
|
||||
@@ -39,9 +42,14 @@ export class ManagerComponent implements OnInit {
|
||||
|
||||
setSelectedTrigger(triggerKey: TriggerKey) {
|
||||
this.selectedTriggerKey = triggerKey;
|
||||
this.monitoredTriggerKey = triggerKey;
|
||||
this.newTriggerFormOpened = false;
|
||||
}
|
||||
|
||||
monitorTrigger(triggerKey: TriggerKey) {
|
||||
this.monitoredTriggerKey = triggerKey;
|
||||
}
|
||||
|
||||
setNewTriggerFormOpened(opened: boolean) {
|
||||
this.newTriggerFormOpened = opened;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user