mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-14 22:00:30 +09:00
#103 final step into multi-trigger feature
This commit is contained in:
19
quartz-manager-frontend/.eslintrc.sonar.json
Normal file
19
quartz-manager-frontend/.eslintrc.sonar.json
Normal 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
|
||||||
|
}
|
||||||
20
quartz-manager-frontend/package-lock.json
generated
20
quartz-manager-frontend/package-lock.json
generated
@@ -62,6 +62,7 @@
|
|||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-sonarjs": "^0.16.0",
|
||||||
"jasmine-core": "~4.5.0",
|
"jasmine-core": "~4.5.0",
|
||||||
"jasmine-spec-reporter": "~7.0.0",
|
"jasmine-spec-reporter": "~7.0.0",
|
||||||
"jest": "28.1.3",
|
"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": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -26048,6 +26061,13 @@
|
|||||||
"prettier-linter-helpers": "^1.0.0"
|
"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": {
|
"eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
"build": "ng build --configuration production",
|
"build": "ng build --configuration production",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"lint": "ng lint",
|
"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"
|
"e2e": "ng e2e"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-sonarjs": "^0.16.0",
|
||||||
"jasmine-core": "~4.5.0",
|
"jasmine-core": "~4.5.0",
|
||||||
"jasmine-spec-reporter": "~7.0.0",
|
"jasmine-spec-reporter": "~7.0.0",
|
||||||
"jest": "28.1.3",
|
"jest": "28.1.3",
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
} from '@angular/platform-browser-dynamic/testing';
|
} from '@angular/platform-browser-dynamic/testing';
|
||||||
|
|
||||||
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
|
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
|
||||||
declare var __karma__: any;
|
declare let __karma__: any;
|
||||||
declare var require: any;
|
declare let require: any;
|
||||||
|
|
||||||
// Prevent Karma from running prematurely.
|
// Prevent Karma from running prematurely.
|
||||||
__karma__.loaded = function () {};
|
__karma__.loaded = function () {};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -27,37 +27,42 @@ export class LogsPanelComponent implements OnInit, OnDestroy {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set triggerKey(triggerKey: TriggerKey) {
|
set triggerKey(triggerKey: TriggerKey) {
|
||||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
if (!triggerKey || !triggerKey.name) {
|
||||||
if (this.selectedTriggerKey && this.selectedTriggerKey.name) {
|
this._unsubscribeFromTopic();
|
||||||
this._subscribeToTheTopic(this.selectedTriggerKey);
|
this.selectedTriggerKey = null;
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||||
|
this._subscribeToTheTopic(this.selectedTriggerKey);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
|
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
|
||||||
if (this.topicSubscription) {
|
this._unsubscribeFromTopic();
|
||||||
this.topicSubscription.unsubscribe();
|
this.topicSubscription = this.logsRxWebsocketService.watch(`/topic/logs/${triggerKey.name}`)
|
||||||
}
|
.pipe(map((msg: any) => JSON.parse(msg.body)))
|
||||||
this.topicSubscription = this.logsRxWebsocketService.watch(`/topic/logs/${triggerKey.name}`)
|
|
||||||
.pipe(map(msg => JSON.parse(msg.body)))
|
|
||||||
.subscribe(this._showNewLog, (err) => {
|
.subscribe(this._showNewLog, (err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
// TODO in case of 401
|
// TODO in case of 401
|
||||||
// this.apiService.get('/quartz-manager/session/refresh');
|
// this.apiService.get('/quartz-manager/session/refresh');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.topicSubscription) {
|
this._unsubscribeFromTopic();
|
||||||
this.topicSubscription.unsubscribe();
|
}
|
||||||
}
|
|
||||||
this.topicSubscription.unsubscribe();
|
private _unsubscribeFromTopic() {
|
||||||
this.topicSubscription = null;
|
if (this.topicSubscription) {
|
||||||
}
|
this.topicSubscription.unsubscribe();
|
||||||
|
this.topicSubscription = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_showNewLog = (logRecord) => {
|
_showNewLog = (logRecord) => {
|
||||||
if (this.logs.length > this.MAX_LOGS) {
|
if (this.logs.length > this.MAX_LOGS) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -21,20 +21,22 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
|
|||||||
private progressRxWebsocketService: ProgressRxWebsocketService
|
private progressRxWebsocketService: ProgressRxWebsocketService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set triggerKey(triggerKey: TriggerKey) {
|
set triggerKey(triggerKey: TriggerKey) {
|
||||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
if (!triggerKey || !triggerKey.name) {
|
||||||
if (this.selectedTriggerKey && this.selectedTriggerKey.name) {
|
this._unsubscribeFromTopic();
|
||||||
this._subscribeToTheTopic(this.selectedTriggerKey);
|
this.selectedTriggerKey = null;
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
|
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||||
if (this.topicSubscription) {
|
this._subscribeToTheTopic(this.selectedTriggerKey);
|
||||||
this.topicSubscription.unsubscribe();
|
}
|
||||||
}
|
|
||||||
this.topicSubscription = this.progressRxWebsocketService.watch(`/topic/progress/${triggerKey.name}`)
|
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
|
||||||
.pipe(map(msg => JSON.parse(msg.body)))
|
this._unsubscribeFromTopic();
|
||||||
|
this.topicSubscription = this.progressRxWebsocketService.watch(`/topic/progress/${triggerKey.name}`)
|
||||||
|
.pipe(map((msg: any) => JSON.parse(msg.body)))
|
||||||
.subscribe(this.onNewProgressMsg, (err) => {
|
.subscribe(this.onNewProgressMsg, (err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
// TODO in case of 401
|
// TODO in case of 401
|
||||||
@@ -48,14 +50,17 @@ export class ProgressPanelComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.topicSubscription) {
|
this._unsubscribeFromTopic();
|
||||||
this.topicSubscription.unsubscribe();
|
}
|
||||||
}
|
|
||||||
this.topicSubscription.unsubscribe();
|
private _unsubscribeFromTopic() {
|
||||||
this.topicSubscription = null;
|
if (this.topicSubscription) {
|
||||||
}
|
this.topicSubscription.unsubscribe();
|
||||||
|
this.topicSubscription = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ import {MatDividerModule} from '@angular/material/divider';
|
|||||||
|
|
||||||
describe('SchedulerControlComponent', () => {
|
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 component: SchedulerControlComponent;
|
||||||
let fixture: ComponentFixture<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', () => {
|
it('should display the play button at the beginning since the scheduler is stopped', () => {
|
||||||
expect(component).toBeDefined();
|
expect(component).toBeDefined();
|
||||||
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
|
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
|
||||||
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'STOPPED', []);
|
const mockScheduler = new Scheduler(schedulerName, schedulerId, stoppedStatus, []);
|
||||||
getSchedulerReq.flush(mockScheduler);
|
getSchedulerReq.flush(mockScheduler);
|
||||||
|
|
||||||
expect(component.scheduler).toEqual(mockScheduler);
|
expect(component.scheduler).toEqual(mockScheduler);
|
||||||
expect(component.scheduler.status).toEqual('STOPPED');
|
expect(component.scheduler.status).toEqual(stoppedStatus);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
||||||
const schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
const schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||||
expect(schedulerBtnDe).toBeTruthy();
|
expect(schedulerBtnDe).toBeTruthy();
|
||||||
|
|
||||||
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
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', () => {
|
it('should switch the button to pause when the scheduler is started', () => {
|
||||||
expect(component).toBeDefined();
|
expect(component).toBeDefined();
|
||||||
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
|
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
|
||||||
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'STOPPED', []);
|
const mockScheduler = new Scheduler(schedulerName, schedulerId, stoppedStatus, []);
|
||||||
getSchedulerReq.flush(mockScheduler);
|
getSchedulerReq.flush(mockScheduler);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
||||||
let schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
let schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||||
expect(schedulerBtnDe).toBeTruthy();
|
expect(schedulerBtnDe).toBeTruthy();
|
||||||
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
||||||
expect(playIconDe).toBeTruthy();
|
expect(playIconDe).toBeTruthy();
|
||||||
@@ -72,7 +78,7 @@ describe('SchedulerControlComponent', () => {
|
|||||||
startSchedulerReq.flush(null);
|
startSchedulerReq.flush(null);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||||
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
|
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
|
||||||
expect(pauseIconDe).toBeTruthy();
|
expect(pauseIconDe).toBeTruthy();
|
||||||
|
|
||||||
@@ -80,13 +86,13 @@ describe('SchedulerControlComponent', () => {
|
|||||||
|
|
||||||
it('should switch the button to play when the scheduler is stopped', () => {
|
it('should switch the button to play when the scheduler is stopped', () => {
|
||||||
expect(component).toBeDefined();
|
expect(component).toBeDefined();
|
||||||
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
|
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
|
||||||
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'RUNNING', []);
|
const mockScheduler = new Scheduler(schedulerName, schedulerId, 'RUNNING', []);
|
||||||
getSchedulerReq.flush(mockScheduler);
|
getSchedulerReq.flush(mockScheduler);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
||||||
let schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
let schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||||
expect(schedulerBtnDe).toBeTruthy();
|
expect(schedulerBtnDe).toBeTruthy();
|
||||||
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
|
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
|
||||||
expect(pauseIconDe).toBeTruthy();
|
expect(pauseIconDe).toBeTruthy();
|
||||||
@@ -96,7 +102,7 @@ describe('SchedulerControlComponent', () => {
|
|||||||
startSchedulerReq.flush(null);
|
startSchedulerReq.flush(null);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||||
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
||||||
expect(playIconDe).toBeTruthy();
|
expect(playIconDe).toBeTruthy();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import {MisfireInstruction} from '../../model/misfire-instruction.model';
|
|||||||
|
|
||||||
describe('SimpleTriggerConfig', () => {
|
describe('SimpleTriggerConfig', () => {
|
||||||
|
|
||||||
|
const submitButtonSelector = 'form button[color="primary"]';
|
||||||
|
const repeatIntervalSelector = '#repeatInterval';
|
||||||
|
const testTriggerName = 'test-trigger';
|
||||||
|
const testJobName = 'TestJob';
|
||||||
|
|
||||||
let component: SimpleTriggerConfigComponent;
|
let component: SimpleTriggerConfigComponent;
|
||||||
let fixture: ComponentFixture<SimpleTriggerConfigComponent>;
|
let fixture: ComponentFixture<SimpleTriggerConfigComponent>;
|
||||||
|
|
||||||
@@ -91,16 +96,16 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const getJobsReq = httpTestingController.expectOne(`${CONTEXT_PATH}/jobs`);
|
const getJobsReq = httpTestingController.expectOne(`${CONTEXT_PATH}/jobs`);
|
||||||
getJobsReq.flush(['TestJob']);
|
getJobsReq.flush([testJobName]);
|
||||||
|
|
||||||
const componentDe: DebugElement = fixture.debugElement;
|
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.textContent.trim()).toEqual('Submit');
|
||||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
||||||
|
|
||||||
setInputValue(componentDe, '#triggerName', 'test-trigger');
|
setInputValue(componentDe, '#triggerName', testTriggerName);
|
||||||
expect(component.simpleTriggerReactiveForm.controls.triggerName.value).toEqual('test-trigger');
|
expect(component.simpleTriggerReactiveForm.controls.triggerName.value).toEqual(testTriggerName);
|
||||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
||||||
setMatSelectValueByIndex(componentDe, '#misfireInstruction', 0);
|
setMatSelectValueByIndex(componentDe, '#misfireInstruction', 0);
|
||||||
expect(component.simpleTriggerReactiveForm.controls.misfireInstruction.value).toEqual('MISFIRE_INSTRUCTION_FIRE_NOW');
|
expect(component.simpleTriggerReactiveForm.controls.misfireInstruction.value).toEqual('MISFIRE_INSTRUCTION_FIRE_NOW');
|
||||||
@@ -111,7 +116,7 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
setInputValue(componentDe, '#repeatCount', '1000');
|
setInputValue(componentDe, '#repeatCount', '1000');
|
||||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
||||||
|
|
||||||
setInputValue(componentDe, '#repeatInterval', '2000');
|
setInputValue(componentDe, repeatIntervalSelector, '2000');
|
||||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual(null);
|
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,18 +127,18 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
it('should emit an event when a new trigger is submitted', () => {
|
it('should emit an event when a new trigger is submitted', () => {
|
||||||
const componentDe: DebugElement = fixture.debugElement;
|
const componentDe: DebugElement = fixture.debugElement;
|
||||||
const mockTrigger = new Trigger();
|
const mockTrigger = new Trigger();
|
||||||
mockTrigger.triggerKeyDTO = new TriggerKey('test-trigger', null);
|
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
|
||||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
|
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
|
||||||
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
|
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
|
||||||
|
|
||||||
openFormAndFillAllMandatoryFields();
|
openFormAndFillAllMandatoryFields();
|
||||||
|
|
||||||
setInputValue(componentDe, '#repeatInterval', '2000');
|
setInputValue(componentDe, repeatIntervalSelector, '2000');
|
||||||
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(2000);
|
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(2000);
|
||||||
setInputValue(componentDe, '#repeatCount', '100');
|
setInputValue(componentDe, '#repeatCount', '100');
|
||||||
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatCount).toEqual(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');
|
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
|
||||||
|
|
||||||
let actualNewTrigger;
|
let actualNewTrigger;
|
||||||
@@ -141,28 +146,28 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
|
|
||||||
submitButton.nativeElement.click();
|
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);
|
postSimpleTriggerReq.flush(mockTrigger);
|
||||||
|
|
||||||
expect(actualNewTrigger).toEqual(mockTrigger);
|
expect(actualNewTrigger).toEqual(mockTrigger);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not emit an event when an existing trigger is edited', () => {
|
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;
|
component.triggerKey = mockTriggerKey;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const mockTrigger = new SimpleTrigger();
|
const mockTrigger = new SimpleTrigger();
|
||||||
mockTrigger.triggerKeyDTO = new TriggerKey('test-trigger', null);
|
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
|
||||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
|
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
|
||||||
mockTrigger.mayFireAgain = true;
|
mockTrigger.mayFireAgain = true;
|
||||||
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
|
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);
|
getSimpleTriggerReq.flush(mockTrigger);
|
||||||
|
|
||||||
component.simpleTriggerReactiveForm.setValue({
|
component.simpleTriggerReactiveForm.setValue({
|
||||||
triggerName: 'test-trigger',
|
triggerName: testTriggerName,
|
||||||
jobClass: 'TestJob',
|
jobClass: testJobName,
|
||||||
triggerRecurrence: {
|
triggerRecurrence: {
|
||||||
repeatInterval: 2000,
|
repeatInterval: 2000,
|
||||||
repeatCount: 100,
|
repeatCount: 100,
|
||||||
@@ -178,10 +183,10 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const componentDe: DebugElement = fixture.debugElement;
|
const componentDe: DebugElement = fixture.debugElement;
|
||||||
setInputValue(componentDe, '#repeatInterval', '4000');
|
setInputValue(componentDe, repeatIntervalSelector, '4000');
|
||||||
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(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');
|
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
|
||||||
|
|
||||||
let actualNewTrigger;
|
let actualNewTrigger;
|
||||||
@@ -189,7 +194,7 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
|
|
||||||
submitButton.nativeElement.click();
|
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);
|
putSimpleTriggerReq.flush(mockTrigger);
|
||||||
|
|
||||||
expect(actualNewTrigger).toBeUndefined();
|
expect(actualNewTrigger).toBeUndefined();
|
||||||
@@ -220,7 +225,7 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const componentDe: DebugElement = fixture.debugElement;
|
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.textContent.trim()).toEqual('Submit');
|
||||||
|
|
||||||
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
|
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
|
||||||
@@ -228,6 +233,25 @@ describe('SimpleTriggerConfig', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should reset the form when a new trigger is selected', () => {
|
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();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
|||||||
@Output()
|
@Output()
|
||||||
onNewTrigger = new EventEmitter<SimpleTrigger>();
|
onNewTrigger = new EventEmitter<SimpleTrigger>();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
triggerFormOpenChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private schedulerService: SchedulerService,
|
private schedulerService: SchedulerService,
|
||||||
@@ -63,30 +66,31 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openTriggerForm() {
|
openTriggerForm() {
|
||||||
// this.selectedTriggerKey = null;
|
|
||||||
// this.trigger = null;
|
|
||||||
// this.simpleTriggerReactiveForm.setValue(new SimpleTriggerReactiveForm());
|
|
||||||
this.enabledTriggerForm = true;
|
this.enabledTriggerForm = true;
|
||||||
|
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private closeTriggerForm() {
|
private closeTriggerForm() {
|
||||||
this.enabledTriggerForm = false;
|
this.enabledTriggerForm = false;
|
||||||
|
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set triggerKey(triggerKey: TriggerKey) {
|
set triggerKey(triggerKey: TriggerKey) {
|
||||||
if (!triggerKey) {
|
if (!triggerKey) {
|
||||||
this.selectedTriggerKey = null;
|
this.openNewTriggerForm();
|
||||||
this.trigger = null;
|
|
||||||
this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm());
|
|
||||||
} else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) {
|
} else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) {
|
||||||
this._resetTheTrigger();
|
this._resetTheTrigger();
|
||||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||||
this.fetchSelectedTrigger();
|
this.fetchSelectedTrigger();
|
||||||
|
this.closeTriggerForm();
|
||||||
}
|
}
|
||||||
this.openTriggerForm();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openNewTriggerForm() {
|
||||||
|
this._resetTheTrigger();
|
||||||
|
this.openTriggerForm();
|
||||||
|
}
|
||||||
|
|
||||||
private _resetTheTrigger() {
|
private _resetTheTrigger() {
|
||||||
this.trigger = null;
|
this.trigger = null;
|
||||||
@@ -111,7 +115,11 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
|||||||
existsATriggerInProgress = (): boolean => this.trigger && this.triggerInProgress;
|
existsATriggerInProgress = (): boolean => this.trigger && this.triggerInProgress;
|
||||||
|
|
||||||
onResetReactiveForm = () => {
|
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();
|
this.closeTriggerForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,8 +143,13 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
|||||||
|
|
||||||
this.closeTriggerForm();
|
this.closeTriggerForm();
|
||||||
}, error => {
|
}, error => {
|
||||||
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
|
if (this.trigger) {
|
||||||
}, () => {this.triggerLoading = true});
|
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
|
||||||
|
}
|
||||||
|
this.triggerLoading = false;
|
||||||
|
}, () => {
|
||||||
|
this.triggerLoading = false
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* BROWSER POLYFILLS
|
* BROWSER POLYFILLS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -51,14 +51,14 @@ import 'core-js/es7/reflect';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* Zone JS is required by Angular itself.
|
* Zone JS is required by Angular itself.
|
||||||
*/
|
*/
|
||||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* APPLICATION IMPORTS
|
* APPLICATION IMPORTS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||||||
*/
|
*/
|
||||||
// import 'intl'; // Run `npm install --save intl`.
|
// import 'intl'; // Run `npm install --save intl`.
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* MATERIAL 2
|
* MATERIAL 2
|
||||||
*/
|
*/
|
||||||
import 'hammerjs/hammer';
|
import 'hammerjs/hammer';
|
||||||
|
|||||||
@@ -2,15 +2,22 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { LogsRxWebsocketService } from './logs.rx-websocket.service';
|
import { LogsRxWebsocketService } from './logs.rx-websocket.service';
|
||||||
import {ApiService} from './api.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', () => {
|
describe('LogsRxWebsocketService', () => {
|
||||||
let service: LogsRxWebsocketService;
|
let service: LogsRxWebsocketService;
|
||||||
|
let configureSpy;
|
||||||
|
let activateSpy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configureSpy = jest.spyOn(RxStomp.prototype, 'configure');
|
||||||
|
activateSpy = jest.spyOn(RxStomp.prototype, 'activate').mockImplementation(() => undefined);
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [HttpClientTestingModule],
|
providers: [
|
||||||
providers: [ApiService]
|
{provide: ApiService, useValue: {getToken: () => 'test-token'}}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
service = TestBed.inject(LogsRxWebsocketService);
|
service = TestBed.inject(LogsRxWebsocketService);
|
||||||
});
|
});
|
||||||
@@ -18,4 +25,15 @@ describe('LogsRxWebsocketService', () => {
|
|||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
expect(service).toBeTruthy();
|
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=');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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=');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -35,7 +35,7 @@ export class UserService {
|
|||||||
this.currentUser = user;
|
this.currentUser = user;
|
||||||
this.router.initialNavigation();
|
this.router.initialNavigation();
|
||||||
}, err => {
|
}, 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;
|
const httpErrorResponse = err as HttpErrorResponse;
|
||||||
if (httpErrorResponse.status === 404) {
|
if (httpErrorResponse.status === 404) {
|
||||||
this.isAnAnonymousUser = true;
|
this.isAnAnonymousUser = true;
|
||||||
|
|||||||
@@ -19,10 +19,11 @@
|
|||||||
<div fxFlex="1 1 350px">
|
<div fxFlex="1 1 350px">
|
||||||
<div fxLayout="row" fxFill>
|
<div fxLayout="row" fxFill>
|
||||||
<div fxLayout="column" fxFill>
|
<div fxLayout="column" fxFill>
|
||||||
<qrzmng-simple-trigger-config fxFill
|
<qrzmng-simple-trigger-config fxFill
|
||||||
[triggerKey]="selectedTriggerKey"
|
[triggerKey]="selectedTriggerKey"
|
||||||
(onNewTrigger)="onNewTriggerCreated($event)">
|
(triggerFormOpenChange)="setNewTriggerFormOpened($event)"
|
||||||
</qrzmng-simple-trigger-config>
|
(onNewTrigger)="onNewTriggerCreated($event)">
|
||||||
|
</qrzmng-simple-trigger-config>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||||
import {
|
|
||||||
ConfigService,
|
|
||||||
UserService
|
|
||||||
} from '../../services';
|
|
||||||
import {SimpleTrigger} from '../../model/simple-trigger.model';
|
import {SimpleTrigger} from '../../model/simple-trigger.model';
|
||||||
import {TriggerKey} from '../../model/triggerKey.model';
|
import {TriggerKey} from '../../model/triggerKey.model';
|
||||||
import {SimpleTriggerConfigComponent} from '../../components/simple-trigger-config';
|
import {SimpleTriggerConfigComponent} from '../../components/simple-trigger-config';
|
||||||
@@ -33,15 +29,24 @@ export class ManagerComponent implements OnInit {
|
|||||||
|
|
||||||
onNewTriggerRequested() {
|
onNewTriggerRequested() {
|
||||||
this.selectedTriggerKey = null;
|
this.selectedTriggerKey = null;
|
||||||
// this.triggerConfigComponent.openTriggerForm();
|
this.newTriggerFormOpened = true;
|
||||||
|
if (this.triggerConfigComponent) {
|
||||||
|
this.triggerConfigComponent.openNewTriggerForm();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewTriggerCreated(newTrigger: SimpleTrigger) {
|
onNewTriggerCreated(newTrigger: SimpleTrigger) {
|
||||||
this.triggerListComponent.onNewTrigger(newTrigger);
|
this.triggerListComponent.onNewTrigger(newTrigger);
|
||||||
|
this.newTriggerFormOpened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedTrigger(triggerKey: TriggerKey) {
|
setSelectedTrigger(triggerKey: TriggerKey) {
|
||||||
this.selectedTriggerKey = triggerKey;
|
this.selectedTriggerKey = triggerKey;
|
||||||
|
this.newTriggerFormOpened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewTriggerFormOpened(opened: boolean) {
|
||||||
|
this.newTriggerFormOpened = opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* BROWSER POLYFILLS
|
* BROWSER POLYFILLS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ import 'core-js/es6/reflect';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* Zone JS is required by Angular itself.
|
* Zone JS is required by Angular itself.
|
||||||
*/
|
*/
|
||||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||||
@@ -59,7 +59,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* APPLICATION IMPORTS
|
* APPLICATION IMPORTS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||||||
*/
|
*/
|
||||||
// import 'intl'; // Run `npm install --save intl`.
|
// import 'intl'; // Run `npm install --save intl`.
|
||||||
|
|
||||||
/***************************************************************************************************
|
/** *************************************************************************************************
|
||||||
* MATERIAL 2
|
* MATERIAL 2
|
||||||
*/
|
*/
|
||||||
import 'hammerjs/hammer';
|
import 'hammerjs/hammer';
|
||||||
|
|||||||
2
quartz-manager-frontend/src/typings.d.ts
vendored
2
quartz-manager-frontend/src/typings.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
/* SystemJS module definition */
|
/* SystemJS module definition */
|
||||||
declare var module: NodeModule;
|
declare let module: NodeModule;
|
||||||
interface NodeModule {
|
interface NodeModule {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|||||||
1
quartz-manager-parent/.gitignore
vendored
1
quartz-manager-parent/.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
.idea
|
.idea
|
||||||
|
/**/*.iml
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
<nexus-staging-maven-plugin.version>1.6.7</nexus-staging-maven-plugin.version>
|
<nexus-staging-maven-plugin.version>1.6.7</nexus-staging-maven-plugin.version>
|
||||||
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
|
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
|
||||||
<maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version>
|
<maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version>
|
||||||
|
<sonar-maven-plugin.version>3.11.0.3922</sonar-maven-plugin.version>
|
||||||
<sonar.organization>fabioformosa</sonar.organization>
|
<sonar.organization>fabioformosa</sonar.organization>
|
||||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||||
<sonar.exclusions>
|
<sonar.exclusions>
|
||||||
@@ -133,6 +134,11 @@
|
|||||||
<artifactId>maven-failsafe-plugin</artifactId>
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
<version>${maven-failsafe-plugin.version}</version>
|
<version>${maven-failsafe-plugin.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.sonarsource.scanner.maven</groupId>
|
||||||
|
<artifactId>sonar-maven-plugin</artifactId>
|
||||||
|
<version>${sonar-maven-plugin.version}</version>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
|||||||
@@ -18,13 +18,17 @@ import java.util.Date;
|
|||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class SimpleTriggerServiceIntegrationTest {
|
class SimpleTriggerServiceIntegrationTest {
|
||||||
|
|
||||||
|
private static final String SAMPLE_JOB_CLASS = "it.fabioformosa.quartzmanager.api.jobs.SampleJob";
|
||||||
|
private static final String SAMPLE_EXTRA_JOB_CLASS = "it.fabioformosa.samplepackage.SampleExtraJob";
|
||||||
|
private static final String FIRST_TRIGGER_SUFFIX = "A";
|
||||||
|
private static final String SECOND_TRIGGER_SUFFIX = "B";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SimpleTriggerService simpleTriggerService;
|
private SimpleTriggerService simpleTriggerService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenASimpleTriggerCommandDTOWithAllData_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException {
|
void givenASimpleTriggerCommandDTOWithAllData_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException {
|
||||||
String simpleTriggerTestName = "simpleTriggerWithAllData";
|
String simpleTriggerTestName = "simpleTriggerWithAllData";
|
||||||
String jobClass = "it.fabioformosa.quartzmanager.api.jobs.SampleJob";
|
|
||||||
Date startDate = new Date();
|
Date startDate = new Date();
|
||||||
Date endDate = DateUtils.addHoursToNow(5);
|
Date endDate = DateUtils.addHoursToNow(5);
|
||||||
int repeatCount = 3;
|
int repeatCount = 3;
|
||||||
@@ -41,7 +45,7 @@ class SimpleTriggerServiceIntegrationTest {
|
|||||||
.repeatCount(repeatCount)
|
.repeatCount(repeatCount)
|
||||||
.repeatInterval(repeatInterval)
|
.repeatInterval(repeatInterval)
|
||||||
.misfireInstruction(misfireInstructionFireNow)
|
.misfireInstruction(misfireInstructionFireNow)
|
||||||
.jobClass(jobClass)
|
.jobClass(SAMPLE_JOB_CLASS)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand);
|
SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand);
|
||||||
@@ -61,12 +65,11 @@ class SimpleTriggerServiceIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
void givenASimpleTriggerCommandDTOWithMissingOptionalField_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException {
|
void givenASimpleTriggerCommandDTOWithMissingOptionalField_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException {
|
||||||
String simpleTriggerTestName = "simpleTriggerWithoutOptionalData";
|
String simpleTriggerTestName = "simpleTriggerWithoutOptionalData";
|
||||||
String jobClass = "it.fabioformosa.quartzmanager.api.jobs.SampleJob";
|
|
||||||
|
|
||||||
SimpleTriggerCommandDTO simpleTriggerCommand = SimpleTriggerCommandDTO.builder()
|
SimpleTriggerCommandDTO simpleTriggerCommand = SimpleTriggerCommandDTO.builder()
|
||||||
.triggerName(simpleTriggerTestName)
|
.triggerName(simpleTriggerTestName)
|
||||||
.simpleTriggerInputDTO(SimpleTriggerInputDTO.builder()
|
.simpleTriggerInputDTO(SimpleTriggerInputDTO.builder()
|
||||||
.jobClass(jobClass)
|
.jobClass(SAMPLE_JOB_CLASS)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand);
|
SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand);
|
||||||
@@ -81,4 +84,49 @@ class SimpleTriggerServiceIntegrationTest {
|
|||||||
Assertions.assertThat(simpleTriggerDTO.getRepeatInterval()).isZero();
|
Assertions.assertThat(simpleTriggerDTO.getRepeatInterval()).isZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenTwoSimpleTriggerCommandDTOsForTheSameJob_whenScheduled_thenShouldCreateTwoTriggers() throws SchedulerException, ClassNotFoundException {
|
||||||
|
String triggerNamePrefix = "sameJobTrigger" + System.nanoTime();
|
||||||
|
|
||||||
|
SimpleTriggerDTO firstTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||||
|
buildSimpleTriggerCommand(triggerNamePrefix + FIRST_TRIGGER_SUFFIX, SAMPLE_JOB_CLASS)
|
||||||
|
);
|
||||||
|
SimpleTriggerDTO secondTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||||
|
buildSimpleTriggerCommand(triggerNamePrefix + SECOND_TRIGGER_SUFFIX, SAMPLE_JOB_CLASS)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assertions.assertThat(firstTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + FIRST_TRIGGER_SUFFIX);
|
||||||
|
Assertions.assertThat(secondTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + SECOND_TRIGGER_SUFFIX);
|
||||||
|
Assertions.assertThat(firstTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_JOB_CLASS);
|
||||||
|
Assertions.assertThat(secondTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_JOB_CLASS);
|
||||||
|
Assertions.assertThat(firstTrigger.getJobKeyDTO().getName()).isNotEqualTo(secondTrigger.getJobKeyDTO().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenTwoSimpleTriggerCommandDTOsForDifferentJobs_whenScheduled_thenShouldCreateTwoTriggers() throws SchedulerException, ClassNotFoundException {
|
||||||
|
String triggerNamePrefix = "differentJobTrigger" + System.nanoTime();
|
||||||
|
|
||||||
|
SimpleTriggerDTO firstTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||||
|
buildSimpleTriggerCommand(triggerNamePrefix + FIRST_TRIGGER_SUFFIX, SAMPLE_JOB_CLASS)
|
||||||
|
);
|
||||||
|
SimpleTriggerDTO secondTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||||
|
buildSimpleTriggerCommand(triggerNamePrefix + SECOND_TRIGGER_SUFFIX, SAMPLE_EXTRA_JOB_CLASS)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assertions.assertThat(firstTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + FIRST_TRIGGER_SUFFIX);
|
||||||
|
Assertions.assertThat(secondTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + SECOND_TRIGGER_SUFFIX);
|
||||||
|
Assertions.assertThat(firstTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_JOB_CLASS);
|
||||||
|
Assertions.assertThat(secondTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_EXTRA_JOB_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimpleTriggerCommandDTO buildSimpleTriggerCommand(String triggerName, String jobClass) {
|
||||||
|
return SimpleTriggerCommandDTO.builder()
|
||||||
|
.triggerName(triggerName)
|
||||||
|
.simpleTriggerInputDTO(SimpleTriggerInputDTO.builder()
|
||||||
|
.jobClass(jobClass)
|
||||||
|
.startDate(DateUtils.addHoursToNow(1))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.api.websockets;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.TriggerFiredBundleDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||||
|
|
||||||
|
import static org.mockito.MockitoAnnotations.openMocks;
|
||||||
|
|
||||||
|
class WebSocketNotifierTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private WebSocketLogsNotifier webSocketLogsNotifier;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private WebSocketProgressNotifier webSocketProgressNotifier;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SimpMessageSendingOperations messagingTemplate;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenATriggerName_whenALogIsSent_thenShouldSendItToTheTriggerLogsTopic() {
|
||||||
|
LogRecord logRecord = new LogRecord(LogRecord.LogType.INFO, "Hello!");
|
||||||
|
|
||||||
|
webSocketLogsNotifier.send("trigger-1", logRecord);
|
||||||
|
|
||||||
|
Mockito.verify(messagingTemplate).convertAndSend("/topic/logs/trigger-1", logRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void givenATriggerName_whenProgressIsSent_thenShouldSendItToTheTriggerProgressTopic() {
|
||||||
|
TriggerFiredBundleDTO triggerFiredBundleDTO = new TriggerFiredBundleDTO();
|
||||||
|
|
||||||
|
webSocketProgressNotifier.send("trigger-1", triggerFiredBundleDTO);
|
||||||
|
|
||||||
|
Mockito.verify(messagingTemplate).convertAndSend("/topic/progress/trigger-1", triggerFiredBundleDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user