#103 final step into multi-trigger feature

This commit is contained in:
Fabio Formosa
2026-05-06 01:22:30 +02:00
parent 412e455907
commit 31658416f5
23 changed files with 496 additions and 117 deletions

View File

@@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"plugin:sonarjs/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"sonarjs"
],
"root": true
}

View File

@@ -62,6 +62,7 @@
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-sonarjs": "^0.16.0",
"jasmine-core": "~4.5.0",
"jasmine-spec-reporter": "~7.0.0",
"jest": "28.1.3",
@@ -8604,6 +8605,18 @@
}
}
},
"node_modules/eslint-plugin-sonarjs": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.16.0.tgz",
"integrity": "sha512-al8ojAzcQW8Eu0tWn841ldhPpPcjrJ59TzzTfAVWR45bWvdAASCmrGl8vK0MWHyKVDdC0i17IGbtQQ1KgxLlVA==",
"dev": true,
"engines": {
"node": ">=14"
},
"peerDependencies": {
"eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"dev": true,
@@ -26048,6 +26061,13 @@
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-sonarjs": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.16.0.tgz",
"integrity": "sha512-al8ojAzcQW8Eu0tWn841ldhPpPcjrJ59TzzTfAVWR45bWvdAASCmrGl8vK0MWHyKVDdC0i17IGbtQQ1KgxLlVA==",
"dev": true,
"requires": {}
},
"eslint-scope": {
"version": "5.1.1",
"dev": true,

View File

@@ -8,6 +8,8 @@
"build": "ng build --configuration production",
"test": "jest",
"lint": "ng lint",
"lint:sonar": "eslint --no-eslintrc -c .eslintrc.sonar.json \"src/**/*.ts\"",
"lint:sonar:fix": "eslint --no-eslintrc -c .eslintrc.sonar.json \"src/**/*.ts\" --fix",
"e2e": "ng e2e"
},
"private": true,
@@ -65,6 +67,7 @@
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-sonarjs": "^0.16.0",
"jasmine-core": "~4.5.0",
"jasmine-spec-reporter": "~7.0.0",
"jest": "28.1.3",

View File

@@ -13,8 +13,8 @@ import {
} from '@angular/platform-browser-dynamic/testing';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;
declare let __karma__: any;
declare let require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};

View File

@@ -0,0 +1,64 @@
import {Subject} from 'rxjs';
import {LogsPanelComponent} from './logs-panel.component';
import {TriggerKey} from '../../model/triggerKey.model';
import {jest} from '@jest/globals';
describe('LogsPanelComponent', () => {
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);
component.triggerKey = new TriggerKey('trigger-1', null);
expect(logsRxWebsocketService.watch).toHaveBeenCalledWith('/topic/logs/trigger-1');
const logRecord = {
date: new Date(),
type: 'INFO',
message: 'job completed',
threadName: 'worker-1'
};
messages.next({body: JSON.stringify(logRecord)});
expect(component.logs[0]).toEqual({
time: logRecord.date.toISOString(),
type: 'INFO',
msg: 'job completed',
threadName: 'worker-1'
});
});
it('should unsubscribe from the previous topic when the trigger changes', () => {
const firstMessages = new Subject<any>();
const secondMessages = new Subject<any>();
const logsRxWebsocketService = {
watch: jest.fn()
.mockReturnValueOnce(firstMessages.asObservable())
.mockReturnValueOnce(secondMessages.asObservable())
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
component.triggerKey = new TriggerKey('trigger-1', null);
const firstSubscription = component.topicSubscription;
jest.spyOn(firstSubscription, 'unsubscribe');
component.triggerKey = new TriggerKey('trigger-2', null);
expect(firstSubscription.unsubscribe).toHaveBeenCalled();
expect(logsRxWebsocketService.watch).toHaveBeenCalledWith('/topic/logs/trigger-2');
});
it('should ignore destroy when no topic was selected', () => {
const logsRxWebsocketService = {
watch: jest.fn()
};
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
expect(() => component.ngOnDestroy()).not.toThrow();
});
});

View File

@@ -27,37 +27,42 @@ export class LogsPanelComponent implements OnInit, OnDestroy {
) {
}
@Input()
set triggerKey(triggerKey: TriggerKey) {
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
if (this.selectedTriggerKey && this.selectedTriggerKey.name) {
this._subscribeToTheTopic(this.selectedTriggerKey);
}
}
@Input()
set triggerKey(triggerKey: TriggerKey) {
if (!triggerKey || !triggerKey.name) {
this._unsubscribeFromTopic();
this.selectedTriggerKey = null;
return;
}
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
this._subscribeToTheTopic(this.selectedTriggerKey);
}
ngOnInit() {
}
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
if (this.topicSubscription) {
this.topicSubscription.unsubscribe();
}
this.topicSubscription = this.logsRxWebsocketService.watch(`/topic/logs/${triggerKey.name}`)
.pipe(map(msg => JSON.parse(msg.body)))
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
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);
// TODO in case of 401
// this.apiService.get('/quartz-manager/session/refresh');
});
};
ngOnDestroy() {
if (this.topicSubscription) {
this.topicSubscription.unsubscribe();
}
this.topicSubscription.unsubscribe();
this.topicSubscription = null;
}
};
ngOnDestroy() {
this._unsubscribeFromTopic();
}
private _unsubscribeFromTopic() {
if (this.topicSubscription) {
this.topicSubscription.unsubscribe();
this.topicSubscription = null;
}
}
_showNewLog = (logRecord) => {
if (this.logs.length > this.MAX_LOGS) {

View File

@@ -0,0 +1,54 @@
import {Subject} from 'rxjs';
import {ProgressPanelComponent} from './progress-panel.component';
import {TriggerKey} from '../../model/triggerKey.model';
import {jest} from '@jest/globals';
describe('ProgressPanelComponent', () => {
it('should subscribe to the selected trigger progress topic', () => {
const messages = new Subject<any>();
const progressRxWebsocketService = {
watch: jest.fn(() => messages.asObservable())
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
component.triggerKey = new TriggerKey('trigger-1', null);
expect(progressRxWebsocketService.watch).toHaveBeenCalledWith('/topic/progress/trigger-1');
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
expect(component.progress.percentage).toEqual(75);
expect(component.percentageStr).toEqual('75%');
});
it('should unsubscribe from the previous topic when the trigger changes', () => {
const firstMessages = new Subject<any>();
const secondMessages = new Subject<any>();
const progressRxWebsocketService = {
watch: jest.fn()
.mockReturnValueOnce(firstMessages.asObservable())
.mockReturnValueOnce(secondMessages.asObservable())
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
component.triggerKey = new TriggerKey('trigger-1', null);
const firstSubscription = component.topicSubscription;
jest.spyOn(firstSubscription, 'unsubscribe');
component.triggerKey = new TriggerKey('trigger-2', null);
expect(firstSubscription.unsubscribe).toHaveBeenCalled();
expect(progressRxWebsocketService.watch).toHaveBeenCalledWith('/topic/progress/trigger-2');
});
it('should ignore destroy when no topic was selected', () => {
const progressRxWebsocketService = {
watch: jest.fn()
};
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
expect(() => component.ngOnDestroy()).not.toThrow();
});
});

View File

@@ -21,20 +21,22 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
private progressRxWebsocketService: ProgressRxWebsocketService
) { }
@Input()
set triggerKey(triggerKey: TriggerKey) {
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
if (this.selectedTriggerKey && this.selectedTriggerKey.name) {
this._subscribeToTheTopic(this.selectedTriggerKey);
}
}
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
if (this.topicSubscription) {
this.topicSubscription.unsubscribe();
}
this.topicSubscription = this.progressRxWebsocketService.watch(`/topic/progress/${triggerKey.name}`)
.pipe(map(msg => JSON.parse(msg.body)))
@Input()
set triggerKey(triggerKey: TriggerKey) {
if (!triggerKey || !triggerKey.name) {
this._unsubscribeFromTopic();
this.selectedTriggerKey = null;
return;
}
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
this._subscribeToTheTopic(this.selectedTriggerKey);
}
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
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);
// TODO in case of 401
@@ -48,14 +50,17 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
}
ngOnInit() {
}
ngOnDestroy() {
if (this.topicSubscription) {
this.topicSubscription.unsubscribe();
}
this.topicSubscription.unsubscribe();
this.topicSubscription = null;
}
}
}
ngOnDestroy() {
this._unsubscribeFromTopic();
}
private _unsubscribeFromTopic() {
if (this.topicSubscription) {
this.topicSubscription.unsubscribe();
this.topicSubscription = null;
}
}
}

View File

@@ -13,6 +13,12 @@ import {MatDividerModule} from '@angular/material/divider';
describe('SchedulerControlComponent', () => {
const schedulerUrl = '/quartz-manager/scheduler';
const schedulerButtonSelector = '#schedulerControllerBtn';
const schedulerName = 'test-scheduler';
const schedulerId = 'test-id';
const stoppedStatus = 'STOPPED';
let component: SchedulerControlComponent;
let fixture: ComponentFixture<SchedulerControlComponent>;
@@ -38,16 +44,16 @@ describe('SchedulerControlComponent', () => {
it('should display the play button at the beginning since the scheduler is stopped', () => {
expect(component).toBeDefined();
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'STOPPED', []);
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
const mockScheduler = new Scheduler(schedulerName, schedulerId, stoppedStatus, []);
getSchedulerReq.flush(mockScheduler);
expect(component.scheduler).toEqual(mockScheduler);
expect(component.scheduler.status).toEqual('STOPPED');
expect(component.scheduler.status).toEqual(stoppedStatus);
fixture.detectChanges();
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
const schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
const schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
expect(schedulerBtnDe).toBeTruthy();
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
@@ -56,13 +62,13 @@ describe('SchedulerControlComponent', () => {
it('should switch the button to pause when the scheduler is started', () => {
expect(component).toBeDefined();
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'STOPPED', []);
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
const mockScheduler = new Scheduler(schedulerName, schedulerId, stoppedStatus, []);
getSchedulerReq.flush(mockScheduler);
fixture.detectChanges();
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
let schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
let schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
expect(schedulerBtnDe).toBeTruthy();
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
expect(playIconDe).toBeTruthy();
@@ -72,7 +78,7 @@ describe('SchedulerControlComponent', () => {
startSchedulerReq.flush(null);
fixture.detectChanges();
schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
expect(pauseIconDe).toBeTruthy();
@@ -80,13 +86,13 @@ describe('SchedulerControlComponent', () => {
it('should switch the button to play when the scheduler is stopped', () => {
expect(component).toBeDefined();
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'RUNNING', []);
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
const mockScheduler = new Scheduler(schedulerName, schedulerId, 'RUNNING', []);
getSchedulerReq.flush(mockScheduler);
fixture.detectChanges();
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
let schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
let schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
expect(schedulerBtnDe).toBeTruthy();
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
expect(pauseIconDe).toBeTruthy();
@@ -96,7 +102,7 @@ describe('SchedulerControlComponent', () => {
startSchedulerReq.flush(null);
fixture.detectChanges();
schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
expect(playIconDe).toBeTruthy();

View File

@@ -23,6 +23,11 @@ import {MisfireInstruction} from '../../model/misfire-instruction.model';
describe('SimpleTriggerConfig', () => {
const submitButtonSelector = 'form button[color="primary"]';
const repeatIntervalSelector = '#repeatInterval';
const testTriggerName = 'test-trigger';
const testJobName = 'TestJob';
let component: SimpleTriggerConfigComponent;
let fixture: ComponentFixture<SimpleTriggerConfigComponent>;
@@ -91,16 +96,16 @@ describe('SimpleTriggerConfig', () => {
fixture.detectChanges();
const getJobsReq = httpTestingController.expectOne(`${CONTEXT_PATH}/jobs`);
getJobsReq.flush(['TestJob']);
getJobsReq.flush([testJobName]);
const componentDe: DebugElement = fixture.debugElement;
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
const submitButton = componentDe.query(By.css(submitButtonSelector));
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
setInputValue(componentDe, '#triggerName', 'test-trigger');
expect(component.simpleTriggerReactiveForm.controls.triggerName.value).toEqual('test-trigger');
setInputValue(componentDe, '#triggerName', testTriggerName);
expect(component.simpleTriggerReactiveForm.controls.triggerName.value).toEqual(testTriggerName);
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
setMatSelectValueByIndex(componentDe, '#misfireInstruction', 0);
expect(component.simpleTriggerReactiveForm.controls.misfireInstruction.value).toEqual('MISFIRE_INSTRUCTION_FIRE_NOW');
@@ -111,7 +116,7 @@ describe('SimpleTriggerConfig', () => {
setInputValue(componentDe, '#repeatCount', '1000');
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
setInputValue(componentDe, '#repeatInterval', '2000');
setInputValue(componentDe, repeatIntervalSelector, '2000');
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual(null);
}
@@ -122,18 +127,18 @@ describe('SimpleTriggerConfig', () => {
it('should emit an event when a new trigger is submitted', () => {
const componentDe: DebugElement = fixture.debugElement;
const mockTrigger = new Trigger();
mockTrigger.triggerKeyDTO = new TriggerKey('test-trigger', null);
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
openFormAndFillAllMandatoryFields();
setInputValue(componentDe, '#repeatInterval', '2000');
setInputValue(componentDe, repeatIntervalSelector, '2000');
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(2000);
setInputValue(componentDe, '#repeatCount', '100');
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatCount).toEqual(100);
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
const submitButton = componentDe.query(By.css(submitButtonSelector));
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
let actualNewTrigger;
@@ -141,28 +146,28 @@ describe('SimpleTriggerConfig', () => {
submitButton.nativeElement.click();
const postSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/test-trigger`);
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('test-trigger', null);
const mockTriggerKey = new TriggerKey(testTriggerName, null);
component.triggerKey = mockTriggerKey;
fixture.detectChanges();
const mockTrigger = new SimpleTrigger();
mockTrigger.triggerKeyDTO = new TriggerKey('test-trigger', null);
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
mockTrigger.mayFireAgain = true;
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/test-trigger`);
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
getSimpleTriggerReq.flush(mockTrigger);
component.simpleTriggerReactiveForm.setValue({
triggerName: 'test-trigger',
jobClass: 'TestJob',
triggerName: testTriggerName,
jobClass: testJobName,
triggerRecurrence: {
repeatInterval: 2000,
repeatCount: 100,
@@ -178,10 +183,10 @@ describe('SimpleTriggerConfig', () => {
fixture.detectChanges();
const componentDe: DebugElement = fixture.debugElement;
setInputValue(componentDe, '#repeatInterval', '4000');
setInputValue(componentDe, repeatIntervalSelector, '4000');
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(4000);
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
const submitButton = componentDe.query(By.css(submitButtonSelector));
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
let actualNewTrigger;
@@ -189,7 +194,7 @@ describe('SimpleTriggerConfig', () => {
submitButton.nativeElement.click();
const putSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/test-trigger`);
const putSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
putSimpleTriggerReq.flush(mockTrigger);
expect(actualNewTrigger).toBeUndefined();
@@ -220,7 +225,7 @@ describe('SimpleTriggerConfig', () => {
fixture.detectChanges();
const componentDe: DebugElement = fixture.debugElement;
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
const submitButton = componentDe.query(By.css(submitButtonSelector));
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
@@ -228,6 +233,25 @@ describe('SimpleTriggerConfig', () => {
});
it('should reset the form when a new trigger is selected', () => {
const mockTriggerKey = new TriggerKey(testTriggerName, null);
component.triggerKey = mockTriggerKey;
const mockTrigger = new SimpleTrigger();
mockTrigger.triggerKeyDTO = mockTriggerKey;
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
mockTrigger.mayFireAgain = true;
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
getSimpleTriggerReq.flush(mockTrigger);
expect(component.simpleTriggerReactiveForm.value.triggerName).toEqual(testTriggerName);
component.triggerKey = null;
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
expect(component.simpleTriggerReactiveForm.value.jobClass).toBeNull();
expect(component.shouldShowTheTriggerCardContent()).toBeTruthy();
});

View File

@@ -47,6 +47,9 @@ export class SimpleTriggerConfigComponent implements OnInit {
@Output()
onNewTrigger = new EventEmitter<SimpleTrigger>();
@Output()
triggerFormOpenChange = new EventEmitter<boolean>();
constructor(
private formBuilder: UntypedFormBuilder,
private schedulerService: SchedulerService,
@@ -63,30 +66,31 @@ export class SimpleTriggerConfigComponent implements OnInit {
}
openTriggerForm() {
// this.selectedTriggerKey = null;
// this.trigger = null;
// this.simpleTriggerReactiveForm.setValue(new SimpleTriggerReactiveForm());
this.enabledTriggerForm = true;
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
}
private closeTriggerForm() {
this.enabledTriggerForm = false;
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
}
@Input()
set triggerKey(triggerKey: TriggerKey) {
if (!triggerKey) {
this.selectedTriggerKey = null;
this.trigger = null;
this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm());
this.openNewTriggerForm();
} else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) {
this._resetTheTrigger();
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
this.fetchSelectedTrigger();
this.closeTriggerForm();
}
this.openTriggerForm();
}
openNewTriggerForm() {
this._resetTheTrigger();
this.openTriggerForm();
}
private _resetTheTrigger() {
this.trigger = null;
@@ -111,7 +115,11 @@ export class SimpleTriggerConfigComponent implements OnInit {
existsATriggerInProgress = (): boolean => this.trigger && this.triggerInProgress;
onResetReactiveForm = () => {
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
if (this.trigger) {
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
} else {
this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm());
}
this.closeTriggerForm();
};
@@ -135,8 +143,13 @@ export class SimpleTriggerConfigComponent implements OnInit {
this.closeTriggerForm();
}, error => {
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
}, () => {this.triggerLoading = true});
if (this.trigger) {
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
}
this.triggerLoading = false;
}, () => {
this.triggerLoading = false
});
}

View File

@@ -14,7 +14,7 @@
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
/** *************************************************************************************************
* BROWSER POLYFILLS
*/
@@ -51,14 +51,14 @@ import 'core-js/es7/reflect';
/***************************************************************************************************
/** *************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
/** *************************************************************************************************
* APPLICATION IMPORTS
*/
@@ -68,7 +68,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
*/
// import 'intl'; // Run `npm install --save intl`.
/***************************************************************************************************
/** *************************************************************************************************
* MATERIAL 2
*/
import 'hammerjs/hammer';

View File

@@ -2,15 +2,22 @@ import { TestBed } from '@angular/core/testing';
import { LogsRxWebsocketService } from './logs.rx-websocket.service';
import {ApiService} from './api.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {RxStomp} from '@stomp/rx-stomp';
import {jest} from '@jest/globals';
describe('LogsRxWebsocketService', () => {
let service: LogsRxWebsocketService;
let configureSpy;
let activateSpy;
beforeEach(() => {
configureSpy = jest.spyOn(RxStomp.prototype, 'configure');
activateSpy = jest.spyOn(RxStomp.prototype, 'activate').mockImplementation(() => undefined);
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ApiService]
providers: [
{provide: ApiService, useValue: {getToken: () => 'test-token'}}
]
});
service = TestBed.inject(LogsRxWebsocketService);
});
@@ -18,4 +25,15 @@ describe('LogsRxWebsocketService', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should configure rx-stomp with the logs websocket endpoint', () => {
expect(configureSpy).toHaveBeenCalled();
expect(activateSpy).toHaveBeenCalled();
const config = configureSpy.mock.calls[configureSpy.mock.calls.length - 1][0];
expect(config.heartbeatIncoming).toEqual(0);
expect(config.heartbeatOutgoing).toEqual(20000);
expect(config.reconnectDelay).toEqual(200);
expect(config.webSocketFactory.toString()).toContain('/logs?access_token=');
});
});

View File

@@ -0,0 +1,39 @@
import { TestBed } from '@angular/core/testing';
import {RxStomp} from '@stomp/rx-stomp';
import {jest} from '@jest/globals';
import { ProgressRxWebsocketService } from './progress.rx-websocket.service';
import {ApiService} from './api.service';
describe('ProgressRxWebsocketService', () => {
let service: ProgressRxWebsocketService;
let configureSpy;
let activateSpy;
beforeEach(() => {
configureSpy = jest.spyOn(RxStomp.prototype, 'configure');
activateSpy = jest.spyOn(RxStomp.prototype, 'activate').mockImplementation(() => undefined);
TestBed.configureTestingModule({
providers: [
{provide: ApiService, useValue: {getToken: () => 'test-token'}}
]
});
service = TestBed.inject(ProgressRxWebsocketService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should configure rx-stomp with the progress websocket endpoint', () => {
expect(configureSpy).toHaveBeenCalled();
expect(activateSpy).toHaveBeenCalled();
const config = configureSpy.mock.calls[configureSpy.mock.calls.length - 1][0];
expect(config.heartbeatIncoming).toEqual(0);
expect(config.heartbeatOutgoing).toEqual(20000);
expect(config.reconnectDelay).toEqual(200);
expect(config.webSocketFactory.toString()).toContain('/progress?access_token=');
});
});

View File

@@ -35,7 +35,7 @@ export class UserService {
this.currentUser = user;
this.router.initialNavigation();
}, err => {
console.log(`error retrieving current user due to ` + JSON.stringify(err));
console.log('error retrieving current user due to ' + JSON.stringify(err));
const httpErrorResponse = err as HttpErrorResponse;
if (httpErrorResponse.status === 404) {
this.isAnAnonymousUser = true;

View File

@@ -19,10 +19,11 @@
<div fxFlex="1 1 350px">
<div fxLayout="row" fxFill>
<div fxLayout="column" fxFill>
<qrzmng-simple-trigger-config fxFill
[triggerKey]="selectedTriggerKey"
(onNewTrigger)="onNewTriggerCreated($event)">
</qrzmng-simple-trigger-config>
<qrzmng-simple-trigger-config fxFill
[triggerKey]="selectedTriggerKey"
(triggerFormOpenChange)="setNewTriggerFormOpened($event)"
(onNewTrigger)="onNewTriggerCreated($event)">
</qrzmng-simple-trigger-config>
</div>
</div>
</div>

View File

@@ -1,8 +1,4 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {
ConfigService,
UserService
} from '../../services';
import {SimpleTrigger} from '../../model/simple-trigger.model';
import {TriggerKey} from '../../model/triggerKey.model';
import {SimpleTriggerConfigComponent} from '../../components/simple-trigger-config';
@@ -33,15 +29,24 @@ export class ManagerComponent implements OnInit {
onNewTriggerRequested() {
this.selectedTriggerKey = null;
// this.triggerConfigComponent.openTriggerForm();
this.newTriggerFormOpened = true;
if (this.triggerConfigComponent) {
this.triggerConfigComponent.openNewTriggerForm();
}
}
onNewTriggerCreated(newTrigger: SimpleTrigger) {
this.triggerListComponent.onNewTrigger(newTrigger);
this.newTriggerFormOpened = false;
}
setSelectedTrigger(triggerKey: TriggerKey) {
this.selectedTriggerKey = triggerKey;
this.newTriggerFormOpened = false;
}
setNewTriggerFormOpened(opened: boolean) {
this.newTriggerFormOpened = opened;
}
}

View File

@@ -14,7 +14,7 @@
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
/** *************************************************************************************************
* BROWSER POLYFILLS
*/
@@ -50,7 +50,7 @@ import 'core-js/es6/reflect';
/***************************************************************************************************
/** *************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
@@ -59,7 +59,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
/** *************************************************************************************************
* APPLICATION IMPORTS
*/
@@ -69,7 +69,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
*/
// import 'intl'; // Run `npm install --save intl`.
/***************************************************************************************************
/** *************************************************************************************************
* MATERIAL 2
*/
import 'hammerjs/hammer';

View File

@@ -1,5 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
declare let module: NodeModule;
interface NodeModule {
id: string;
}