From a3b92443c41e97c064e7ec4e39231cb9489b2b85 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sun, 18 Dec 2022 11:53:32 +0100 Subject: [PATCH 01/40] #103 enabled a dev profile in the angular.json --- quartz-manager-frontend/angular.json | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/quartz-manager-frontend/angular.json b/quartz-manager-frontend/angular.json index 86c6522..307e06d 100644 --- a/quartz-manager-frontend/angular.json +++ b/quartz-manager-frontend/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "angular-spring-starter": { + "quartz-manager-ui": { "root": "", "prefix": "qrzmng", "sourceRoot": "src", @@ -31,6 +31,14 @@ "scripts": [] }, "configurations": { + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + }, "production": { "budgets": [ { @@ -58,18 +66,18 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "angular-spring-starter:build" + "browserTarget": "quartz-manager-ui:build:development" }, "configurations": { "production": { - "browserTarget": "angular-spring-starter:build:production" + "browserTarget": "quartz-manager-ui:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "angular-spring-starter:build" + "browserTarget": "quartz-manager-ui:build" } }, "lint": { @@ -81,7 +89,7 @@ } } }, - "angular-spring-starter-e2e": { + "quartz-manager-ui-e2e": { "root": "e2e", "sourceRoot": "e2e", "projectType": "application", @@ -90,7 +98,7 @@ "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "./protractor.conf.js", - "devServerTarget": "angular-spring-starter:serve" + "devServerTarget": "quartz-manager-ui:serve" } }, "lint": { From ac63576704116602ab41a3df2ad1016cf81158e6 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sun, 18 Dec 2022 11:58:24 +0100 Subject: [PATCH 02/40] #103 migrated the logs websocket to rx-stomp --- quartz-manager-frontend/package-lock.json | 54 +++++++++++------- quartz-manager-frontend/package.json | 2 +- quartz-manager-frontend/src/app/app.module.ts | 35 +----------- .../logs-panel/logs-panel.component.ts | 56 +++++++++++++------ .../logs.rx-websocket.service.spec.ts | 16 ++++++ .../app/services/logs.rx-websocket.service.ts | 23 ++++++++ .../src/app/services/rx-stomp.service.ts | 12 ++++ .../src/app/services/websocket.service.ts | 4 +- 8 files changed, 129 insertions(+), 73 deletions(-) create mode 100644 quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts create mode 100644 quartz-manager-frontend/src/app/services/logs.rx-websocket.service.ts create mode 100644 quartz-manager-frontend/src/app/services/rx-stomp.service.ts diff --git a/quartz-manager-frontend/package-lock.json b/quartz-manager-frontend/package-lock.json index f3bbc29..f5bb1db 100644 --- a/quartz-manager-frontend/package-lock.json +++ b/quartz-manager-frontend/package-lock.json @@ -27,7 +27,7 @@ "@fortawesome/fontawesome": "^1.1.4", "@fortawesome/fontawesome-free-regular": "^5.0.8", "@fortawesome/fontawesome-free-solid": "^5.0.8", - "@stomp/ng2-stompjs": "^0.6.3", + "@stomp/rx-stomp": "1.2.0", "core-js": "2.5.1", "hammerjs": "2.0.8", "moment": "^2.29.1", @@ -4229,19 +4229,19 @@ "dev": true, "license": "MIT" }, - "node_modules/@stomp/ng2-stompjs": { - "version": "0.6.4", - "license": "MIT", + "node_modules/@stomp/rx-stomp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stomp/rx-stomp/-/rx-stomp-1.2.0.tgz", + "integrity": "sha512-QLzPe3q0EwLB+cVWdUFEO4z5tyR+kPnXJANKN2UvB7Spz/oViHF959cydmXdQWaK7NHp86VO54TgFfXbHVnSLg==", "dependencies": { - "@stomp/stompjs": "^4.0.0 >=4.0.2" + "@stomp/stompjs": "^6.0.0 >=6.1.1", + "angular2-uuid": "^1.1.1" } }, - "node_modules/@stomp/stompjs": { - "version": "4.0.8", - "license": "Apache-2.0", - "optionalDependencies": { - "websocket": "^1.0.24" - } + "node_modules/@stomp/rx-stomp/node_modules/@stomp/stompjs": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-6.1.2.tgz", + "integrity": "sha512-FHDTrIFM5Ospi4L3Xhj6v2+NzCVAeNDcBe95YjUWhWiRMrBF6uN3I7AUOlRgT6jU/2WQvvYK8ZaIxFfxFp+uHQ==" }, "node_modules/@tootallnate/once": { "version": "2.0.0", @@ -5419,6 +5419,11 @@ "ajv": "^8.8.2" } }, + "node_modules/angular2-uuid": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/angular2-uuid/-/angular2-uuid-1.1.1.tgz", + "integrity": "sha512-6AXPyii9q8KBFGagybLNVmdGJLPcVZAhmv3odNGSJIA18LuJ3xOe6uN9GvjlQsGfdmYeuxlsGnFEUu7gPhkc+g==" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -22763,16 +22768,20 @@ "version": "3.1.0", "dev": true }, - "@stomp/ng2-stompjs": { - "version": "0.6.4", + "@stomp/rx-stomp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@stomp/rx-stomp/-/rx-stomp-1.2.0.tgz", + "integrity": "sha512-QLzPe3q0EwLB+cVWdUFEO4z5tyR+kPnXJANKN2UvB7Spz/oViHF959cydmXdQWaK7NHp86VO54TgFfXbHVnSLg==", "requires": { - "@stomp/stompjs": "^4.0.0 >=4.0.2" - } - }, - "@stomp/stompjs": { - "version": "4.0.8", - "requires": { - "websocket": "^1.0.24" + "@stomp/stompjs": "^6.0.0 >=6.1.1", + "angular2-uuid": "^1.1.1" + }, + "dependencies": { + "@stomp/stompjs": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-6.1.2.tgz", + "integrity": "sha512-FHDTrIFM5Ospi4L3Xhj6v2+NzCVAeNDcBe95YjUWhWiRMrBF6uN3I7AUOlRgT6jU/2WQvvYK8ZaIxFfxFp+uHQ==" + } } }, "@tootallnate/once": { @@ -23670,6 +23679,11 @@ "fast-deep-equal": "^3.1.3" } }, + "angular2-uuid": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/angular2-uuid/-/angular2-uuid-1.1.1.tgz", + "integrity": "sha512-6AXPyii9q8KBFGagybLNVmdGJLPcVZAhmv3odNGSJIA18LuJ3xOe6uN9GvjlQsGfdmYeuxlsGnFEUu7gPhkc+g==" + }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", diff --git a/quartz-manager-frontend/package.json b/quartz-manager-frontend/package.json index e6d640d..b390f5b 100644 --- a/quartz-manager-frontend/package.json +++ b/quartz-manager-frontend/package.json @@ -30,7 +30,7 @@ "@fortawesome/fontawesome": "^1.1.4", "@fortawesome/fontawesome-free-regular": "^5.0.8", "@fortawesome/fontawesome-free-solid": "^5.0.8", - "@stomp/ng2-stompjs": "^0.6.3", + "@stomp/rx-stomp": "1.2.0", "core-js": "2.5.1", "hammerjs": "2.0.8", "moment": "^2.29.1", diff --git a/quartz-manager-frontend/src/app/app.module.ts b/quartz-manager-frontend/src/app/app.module.ts index f521893..8dea7b1 100644 --- a/quartz-manager-frontend/src/app/app.module.ts +++ b/quartz-manager-frontend/src/app/app.module.ts @@ -62,36 +62,12 @@ import { APP_BASE_HREF } from '@angular/common'; import {SimpleTriggerConfigComponent} from './components/simple-trigger-config'; import JobService from './services/job.service'; import {GenericErrorComponent} from './views/error/genericError.component'; +import {LogsRxWebsocketService} from './services/logs.rx-websocket.service'; export function initUserFactory(userService: UserService) { return () => userService.fetchLoggedUser(); } - -// const stompConfig: StompConfig = { -// // Which server? -// url: 'ws://localhost:8080/quartz-manager/progress', - -// // Headers -// // Typical keys: login, passcode, host -// headers: { -// login: 'admin', -// passcode: 'admin' -// }, - -// // How often to heartbeat? -// // Interval in milliseconds, set to 0 to disable -// heartbeat_in: 0, // Typical value 0 - disabled -// heartbeat_out: 20000, // Typical value 20000 - every 20 seconds -// // Wait in milliseconds before attempting auto reconnect -// // Set to 0 to disable -// // Typical value 5000 (5 seconds) -// reconnect_delay: 5000, - -// // Will log diagnostics on console -// debug: true -// }; - export function jwtOptionsFactory(apiService: ApiService) { return { tokenGetter: () => { @@ -169,18 +145,13 @@ export function jwtOptionsFactory(apiService: ApiService) { JobService, TriggerService, ProgressWebsocketService, - LogsWebsocketService, + // LogsWebsocketService, + LogsRxWebsocketService, AuthService, ApiService, UserService, ConfigService, MatIconRegistry - // StompService, - // ServerSocket - // { - // provide: StompConfig, - // useValue: stompConfig - // } ], bootstrap: [AppComponent] }) diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts index a9dc720..d630987 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts @@ -1,42 +1,62 @@ -import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; +import {Component, OnInit, Input, Output, EventEmitter, OnDestroy} from '@angular/core'; -import {LogsWebsocketService, ApiService} from '../../services'; +import {LogsWebsocketService, ApiService, getBaseUrl, CONTEXT_PATH, QuartzManagerWebsocketMessage} from '../../services'; import {Observable} from 'rxjs'; +import {RxStompService, } from '../../services/rx-stomp.service'; +import {RxStompConfig} from '@stomp/rx-stomp/esm6/rx-stomp-config'; +import {LogsRxWebsocketService} from '../../services/logs.rx-websocket.service'; +import {map} from 'rxjs/operators'; + @Component({ selector: 'logs-panel', templateUrl: './logs-panel.component.html', styleUrls: ['./logs-panel.component.scss'] }) -export class LogsPanelComponent implements OnInit { +export class LogsPanelComponent implements OnInit, OnDestroy { MAX_LOGS = 30; logs = new Array(); + topicSubscription; + constructor( - private logsWebsocketService: LogsWebsocketService, + // private logsWebsocketService: LogsWebsocketService, + private logsRxWebsocketService: LogsRxWebsocketService, private apiService: ApiService ) { } ngOnInit() { - const obs = this.logsWebsocketService.getObservable() - obs.subscribe({ - 'next': this.onNewLogMsg, - 'error': (err) => { - console.log(err) - } - }); + // const obs = this.logsWebsocketService.getObservable() + // obs.subscribe({ + // 'next': this.onNewLogMsg, + // 'error': (err) => { + // console.log(err) + // } + // }); + + this.topicSubscription = this.logsRxWebsocketService.watch('/topic/logs') + .pipe(map(msg => JSON.parse(msg.body))) + .subscribe(this._showNewLog, (err) => { + console.log(err); + // TODO in case of 401 + // this.apiService.get('/quartz-manager/session/refresh'); + }); } - onNewLogMsg = (receivedMsg) => { - if (receivedMsg.type === 'SUCCESS') { - this._showNewLog(receivedMsg.message); - } else if (receivedMsg.type === 'ERROR') { - this._refreshSession(); - } // if websocket has been closed for session expiration, try to refresh it - }; + ngOnDestroy() { + this.topicSubscription.unsubscribe(); + } + + // onNewLogMsg = (receivedMsg) => { + // if (receivedMsg.body.type === 'SUCCESS') { + // this._showNewLog(receivedMsg.body.message); + // } else if (receivedMsg.body.type === 'ERROR') { + // this._refreshSession(); + // } // if websocket has been closed for session expiration, try to refresh it + // }; _showNewLog = (logRecord) => { if (this.logs.length > this.MAX_LOGS) { diff --git a/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts new file mode 100644 index 0000000..05dea34 --- /dev/null +++ b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LogsRxWebsocketService } from './logs.rx-websocket.service'; + +describe('LogsRxWebsocketService', () => { + let service: LogsRxWebsocketService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(LogsRxWebsocketService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.ts b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.ts new file mode 100644 index 0000000..76a07b4 --- /dev/null +++ b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import {RxStompService} from './rx-stomp.service'; +import {ApiService} from './api.service'; +import SockJS from 'sockjs-client'; +import {CONTEXT_PATH, getBaseUrl} from './config.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LogsRxWebsocketService extends RxStompService { + + constructor(private apiService: ApiService) { + super({ + webSocketFactory: () => new SockJS(`${getBaseUrl()}${CONTEXT_PATH}/logs?access_token=${this.apiService.getToken()}`), + heartbeatIncoming: 0, + heartbeatOutgoing: 20000, + reconnectDelay: 200, + debug: (msg: string): void => { + console.log(new Date(), msg); + } + }); + } +} diff --git a/quartz-manager-frontend/src/app/services/rx-stomp.service.ts b/quartz-manager-frontend/src/app/services/rx-stomp.service.ts new file mode 100644 index 0000000..dfdd16a --- /dev/null +++ b/quartz-manager-frontend/src/app/services/rx-stomp.service.ts @@ -0,0 +1,12 @@ +import {RxStomp} from '@stomp/rx-stomp'; +import {RxStompConfig} from '@stomp/rx-stomp/esm6/rx-stomp-config'; + +export class RxStompService extends RxStomp { + + constructor(rxStompConfig: RxStompConfig) { + super(); + super.configure(rxStompConfig); + super.activate(); + } + +} diff --git a/quartz-manager-frontend/src/app/services/websocket.service.ts b/quartz-manager-frontend/src/app/services/websocket.service.ts index 599f936..11b4342 100644 --- a/quartz-manager-frontend/src/app/services/websocket.service.ts +++ b/quartz-manager-frontend/src/app/services/websocket.service.ts @@ -77,7 +77,7 @@ export class WebsocketService { }; _socketListener = (frame) => { - console.log('Connected: ' + frame); + console.log(`Connected to ${this._options.socketUrl}: ${frame}`); this._socket.stomp.subscribe( this._options.topicName, data => this.subscribers.forEach(subscriber => subscriber.observer.next(this.getMessage(data))) @@ -94,7 +94,7 @@ export class WebsocketService { scheduleReconnection = () => { this.reconnectionPromise = setTimeout(() => { - console.log('Socket reconnecting... (if it fails, next attempt in ' + this._options.reconnectionTimeout + ' msec)'); + console.log(`Socket reconnecting to ${this._options.socketUrl}... (if it fails, next attempt in ${this._options.reconnectionTimeout} msec)`); this.connect(); }, this._options.reconnectionTimeout); } From 261dd8b624190155e82ad23e6f5ca05290634bb8 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 20 Dec 2022 20:51:10 +0100 Subject: [PATCH 03/40] #103 appended the trigger key to the websocket topic --- .../logs-panel/logs-panel.component.ts | 35 +++++++++++++++++-- .../simple-trigger-config.component.ts | 6 ++-- .../trigger-list/trigger-list.component.ts | 2 +- .../app/views/manager/manager.component.html | 5 ++- .../api/jobs/AbstractQuartzManagerJob.java | 6 ++-- .../api/websockets/WebSocketLogsNotifier.java | 4 +-- .../websockets/WebSocketProgressNotifier.java | 2 +- .../api/websockets/WebhookSender.java | 2 +- .../quartzmanager/api/jobs/SampleJobTest.java | 6 ++-- 9 files changed, 53 insertions(+), 15 deletions(-) diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts index d630987..069747b 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts @@ -2,10 +2,12 @@ import {Component, OnInit, Input, Output, EventEmitter, OnDestroy} from '@angula import {LogsWebsocketService, ApiService, getBaseUrl, CONTEXT_PATH, QuartzManagerWebsocketMessage} from '../../services'; import {Observable} from 'rxjs'; -import {RxStompService, } from '../../services/rx-stomp.service'; +import {RxStompService,} from '../../services/rx-stomp.service'; import {RxStompConfig} from '@stomp/rx-stomp/esm6/rx-stomp-config'; import {LogsRxWebsocketService} from '../../services/logs.rx-websocket.service'; import {map} from 'rxjs/operators'; +import {Trigger} from '../../model/trigger.model'; +import {TriggerKey} from '../../model/triggerKey.model'; @Component({ @@ -21,6 +23,8 @@ export class LogsPanelComponent implements OnInit, OnDestroy { topicSubscription; + private selectedTriggerKey: TriggerKey; + constructor( // private logsWebsocketService: LogsWebsocketService, private logsRxWebsocketService: LogsRxWebsocketService, @@ -28,6 +32,14 @@ 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); + } + } + ngOnInit() { // const obs = this.logsWebsocketService.getObservable() // obs.subscribe({ @@ -37,17 +49,34 @@ export class LogsPanelComponent implements OnInit, OnDestroy { // } // }); - this.topicSubscription = this.logsRxWebsocketService.watch('/topic/logs') + // this.topicSubscription = this.logsRxWebsocketService.watch('/topic/logs') + // .pipe(map(msg => JSON.parse(msg.body))) + // .subscribe(this._showNewLog, (err) => { + // console.log(err); + // // TODO in case of 401 + // // this.apiService.get('/quartz-manager/session/refresh'); + // }); + } + + 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))) .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; } // onNewLogMsg = (receivedMsg) => { diff --git a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts index 1b643ae..15bd6db 100644 --- a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts +++ b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts @@ -73,8 +73,10 @@ export class SimpleTriggerConfigComponent implements OnInit { @Input() set triggerKey(triggerKey: TriggerKey) { - this.selectedTriggerKey = {...triggerKey} as TriggerKey; - this.fetchSelectedTrigger(); + if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name){ + this.selectedTriggerKey = {...triggerKey} as TriggerKey; + this.fetchSelectedTrigger(); + } } diff --git a/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts b/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts index 9e98f91..14e784c 100644 --- a/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts +++ b/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts @@ -90,7 +90,7 @@ export class TriggerListComponent implements OnInit { onNewTrigger(newTrigger: SimpleTrigger) { this.newTriggers = [newTrigger, ...this.newTriggers]; - this.selectedTrigger = newTrigger.triggerKeyDTO; + this.selectTrigger(newTrigger.triggerKeyDTO); } } diff --git a/quartz-manager-frontend/src/app/views/manager/manager.component.html b/quartz-manager-frontend/src/app/views/manager/manager.component.html index 05b11c9..c03d20c 100644 --- a/quartz-manager-frontend/src/app/views/manager/manager.component.html +++ b/quartz-manager-frontend/src/app/views/manager/manager.component.html @@ -30,7 +30,10 @@
- + +
diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/jobs/AbstractQuartzManagerJob.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/jobs/AbstractQuartzManagerJob.java index 609ee8d..20d6c90 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/jobs/AbstractQuartzManagerJob.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/jobs/AbstractQuartzManagerJob.java @@ -38,11 +38,13 @@ public abstract class AbstractQuartzManagerJob implements Job { LogRecord logMsg = doIt(jobExecutionContext); log.info(logMsg.getMessage()); + String triggerName = jobExecutionContext.getTrigger().getKey().getName(); + logMsg.setThreadName(Thread.currentThread().getName()); - webSocketLogsNotifier.send(logMsg); + webSocketLogsNotifier.send(triggerName, logMsg); TriggerFiredBundleDTO triggerFiredBundleDTO = WebSocketProgressNotifier.buildTriggerFiredBundle(jobExecutionContext); - webSocketProgressNotifier.send(triggerFiredBundleDTO); + webSocketProgressNotifier.send(triggerName, triggerFiredBundleDTO); } } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketLogsNotifier.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketLogsNotifier.java index 9ced563..9cdbfb7 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketLogsNotifier.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketLogsNotifier.java @@ -14,7 +14,7 @@ public class WebSocketLogsNotifier implements WebhookSender { private SimpMessageSendingOperations messagingTemplate; @Override - public void send(LogRecord logRecord) { - messagingTemplate.convertAndSend(TOPIC_LOGS, logRecord); + public void send(String triggerName, LogRecord logRecord) { + messagingTemplate.convertAndSend(TOPIC_LOGS + "/" + triggerName, logRecord); } } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java index 7eeba6d..281d744 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java @@ -20,7 +20,7 @@ public class WebSocketProgressNotifier implements WebhookSender { - void send(T message); + void send(String triggerName, T message); } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java index f56bd0d..f233979 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java @@ -32,6 +32,7 @@ class SampleJobTest { @Test void givenASampleJob_whenTheJobIsExecuted_thenTheWebhookSendersAreCalled() { JobExecutionContext jobExecutionContext = Mockito.mock(JobExecutionContext.class); + String triggerName = "test-trigger"; ScheduleBuilder schedulerBuilder = SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(5) @@ -40,6 +41,7 @@ class SampleJobTest { .newJob(SampleJob.class).withIdentity(JobKey.jobKey("test-job")) .build(); Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity(triggerName) .forJob(jobDetail) .withSchedule(schedulerBuilder) .build(); @@ -47,14 +49,14 @@ class SampleJobTest { Mockito.when(jobExecutionContext.getJobDetail()).thenReturn(jobDetail); sampleJob.execute(jobExecutionContext); - Mockito.verify(webSocketLogsNotifier).send(argThat(actualLogRecord -> { + Mockito.verify(webSocketLogsNotifier).send(triggerName, argThat(actualLogRecord -> { Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!"); Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO); Assertions.assertThat(actualLogRecord.getDate()).isNotNull(); Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull(); return true; })); - Mockito.verify(webSocketProgressNotifier).send(argThat(triggerFiredBundleDTO -> { + Mockito.verify(webSocketProgressNotifier).send(triggerName, argThat(triggerFiredBundleDTO -> { Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job"); Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6); Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName()); From e4c771e36433ec1c305c215394ff90346f3a7fc8 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 20 Dec 2022 23:58:51 +0100 Subject: [PATCH 04/40] #103 migrated the progress websocket to rx-stomp --- quartz-manager-frontend/angular.json | 2 +- quartz-manager-frontend/src/app/app.module.ts | 12 +- .../logs-panel/logs-panel.component.ts | 24 ---- .../progress-panel.component.ts | 97 +++++-------- .../src/app/services/index.ts | 5 +- .../app/services/logs.websocket.service.ts | 12 -- .../services/progress.rx-websocket.service.ts | 23 +++ .../services/progress.websocket.service.ts | 12 -- .../src/app/services/websocket.service.ts | 136 ------------------ .../app/views/manager/manager.component.html | 5 +- .../websockets/WebSocketProgressNotifier.java | 2 +- 11 files changed, 73 insertions(+), 257 deletions(-) delete mode 100644 quartz-manager-frontend/src/app/services/logs.websocket.service.ts create mode 100644 quartz-manager-frontend/src/app/services/progress.rx-websocket.service.ts delete mode 100644 quartz-manager-frontend/src/app/services/progress.websocket.service.ts delete mode 100644 quartz-manager-frontend/src/app/services/websocket.service.ts diff --git a/quartz-manager-frontend/angular.json b/quartz-manager-frontend/angular.json index 307e06d..21d6d19 100644 --- a/quartz-manager-frontend/angular.json +++ b/quartz-manager-frontend/angular.json @@ -19,7 +19,7 @@ "tsConfig": "src/tsconfig.app.json", "polyfills": "src/polyfills.ts", "allowedCommonJsDependencies": [ - "stompjs", "sockjs-client", "moment" + "stompjs", "sockjs-client", "moment", "angular2-uuid" ], "assets": [ "src/assets", diff --git a/quartz-manager-frontend/src/app/app.module.ts b/quartz-manager-frontend/src/app/app.module.ts index 8dea7b1..54fbc83 100644 --- a/quartz-manager-frontend/src/app/app.module.ts +++ b/quartz-manager-frontend/src/app/app.module.ts @@ -43,7 +43,8 @@ import { SchedulerControlComponent, LogsPanelComponent, ProgressPanelComponent, - TriggerListComponent + TriggerListComponent, + SimpleTriggerConfigComponent } from './components'; import { @@ -52,17 +53,15 @@ import { UserService, SchedulerService, ConfigService, - ProgressWebsocketService, - LogsWebsocketService, getHtmlBaseUrl, + LogsRxWebsocketService, + ProgressRxWebsocketService, TriggerService } from './services'; import { ForbiddenComponent } from './views/forbidden/forbidden.component'; import { APP_BASE_HREF } from '@angular/common'; -import {SimpleTriggerConfigComponent} from './components/simple-trigger-config'; import JobService from './services/job.service'; import {GenericErrorComponent} from './views/error/genericError.component'; -import {LogsRxWebsocketService} from './services/logs.rx-websocket.service'; export function initUserFactory(userService: UserService) { return () => userService.fetchLoggedUser(); @@ -144,8 +143,7 @@ export function jwtOptionsFactory(apiService: ApiService) { SchedulerService, JobService, TriggerService, - ProgressWebsocketService, - // LogsWebsocketService, + ProgressRxWebsocketService, LogsRxWebsocketService, AuthService, ApiService, diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts index 069747b..ec76d25 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts @@ -26,7 +26,6 @@ export class LogsPanelComponent implements OnInit, OnDestroy { private selectedTriggerKey: TriggerKey; constructor( - // private logsWebsocketService: LogsWebsocketService, private logsRxWebsocketService: LogsRxWebsocketService, private apiService: ApiService ) { @@ -41,21 +40,6 @@ export class LogsPanelComponent implements OnInit, OnDestroy { } ngOnInit() { - // const obs = this.logsWebsocketService.getObservable() - // obs.subscribe({ - // 'next': this.onNewLogMsg, - // 'error': (err) => { - // console.log(err) - // } - // }); - - // this.topicSubscription = this.logsRxWebsocketService.watch('/topic/logs') - // .pipe(map(msg => JSON.parse(msg.body))) - // .subscribe(this._showNewLog, (err) => { - // console.log(err); - // // TODO in case of 401 - // // this.apiService.get('/quartz-manager/session/refresh'); - // }); } private _subscribeToTheTopic = (triggerKey: TriggerKey) => { @@ -79,14 +63,6 @@ export class LogsPanelComponent implements OnInit, OnDestroy { this.topicSubscription = null; } - // onNewLogMsg = (receivedMsg) => { - // if (receivedMsg.body.type === 'SUCCESS') { - // this._showNewLog(receivedMsg.body.message); - // } else if (receivedMsg.body.type === 'ERROR') { - // this._refreshSession(); - // } // if websocket has been closed for session expiration, try to refresh it - // }; - _showNewLog = (logRecord) => { if (this.logs.length > this.MAX_LOGS) { this.logs.pop(); diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts index 0d50777..8cdc5ba 100644 --- a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts @@ -1,84 +1,61 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core' -import {ProgressWebsocketService, QuartzManagerWebsocketMessage} from '../../services'; - -import { Observable } from 'rxjs'; +import {Component, Input, OnDestroy, OnInit} from '@angular/core' import TriggerFiredBundle from '../../model/trigger-fired-bundle.model'; -// import {Message} from '@stomp/stompjs'; - -// import { Subscription } from 'rxjs/Subscription'; -// import {StompService} from '@stomp/ng2-stompjs'; - -// import { QueueingSubject } from 'queueing-subject' -// import websocketConnect from 'rxjs-websockets' -// import 'rxjs/add/operator/share' -// import {ServerSocket} from '../../services/qz.socket.service' +import {TriggerKey} from '../../model/triggerKey.model'; +import {ProgressRxWebsocketService} from '../../services/progress.rx-websocket.service'; +import {map} from 'rxjs/operators'; @Component({ selector: 'progress-panel', templateUrl: './progress-panel.component.html', styleUrls: ['./progress-panel.component.scss'] }) -export class ProgressPanelComponent implements OnInit { +export class ProgressPanelComponent implements OnInit, OnDestroy { progress: TriggerFiredBundle = new TriggerFiredBundle(); percentageStr: string; - // // Stream of messages - // private subscription: Subscription; - // public messages: Observable; - // // Subscription status - // public subscribed: boolean; - // // Array of historic message (bodies) - // public mq: Array = []; - + topicSubscription; + private selectedTriggerKey: TriggerKey; constructor( - private progressWebsocketService: ProgressWebsocketService, - // private _stompService: StompService, - // private serverSocket : ServerSocket + private progressRxWebsocketService: ProgressRxWebsocketService ) { } - onNewProgressMsg = (receivedMsg: QuartzManagerWebsocketMessage) => { - if (receivedMsg.type === 'SUCCESS') { - const newStatus = receivedMsg.message; - this.progress = newStatus; - this.percentageStr = this.progress.percentage + '%'; + @Input() + set triggerKey(triggerKey: TriggerKey) { + this.selectedTriggerKey = {...triggerKey} as TriggerKey; + if (this.selectedTriggerKey && this.selectedTriggerKey.name) { + this._subscribeToTheTopic(this.selectedTriggerKey); } } - ngOnInit() { - const obs = this.progressWebsocketService.getObservable() - obs.subscribe({ - 'next' : this.onNewProgressMsg, - 'error' : (err) => {console.log(err)} - }); + 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))) + .subscribe(this.onNewProgressMsg, (err) => { + console.log(err); + // TODO in case of 401 + // this.apiService.get('/quartz-manager/session/refresh'); + }); + }; - // this.subscribed = false; - // this.subscribe(); - - // this.serverSocket.connect() - // this.socketSubscription = this.serverSocket.messages.subscribe((message: string) => { - // console.log('received message from server: ', message) - // }) + onNewProgressMsg = (receivedMsg) => { + this.progress = receivedMsg; + this.percentageStr = this.progress.percentage + '%'; } - // public subscribe() { - // if (this.subscribed) { - // return; - // } + ngOnInit() { + } - // // Stream of messages - // this.messages = this._stompService.subscribe('/topic/progress'); - - // // Subscribe a function to be run on_next message - // this.subscription = this.messages.subscribe(this.on_next); - - // this.subscribed = true; - // } - - // public on_next = (message: Message) => { - // this.mq.push(message.body + '\n'); - // console.log(message); - // } + ngOnDestroy() { + if (this.topicSubscription) { + this.topicSubscription.unsubscribe(); + } + this.topicSubscription.unsubscribe(); + this.topicSubscription = null; + } } diff --git a/quartz-manager-frontend/src/app/services/index.ts b/quartz-manager-frontend/src/app/services/index.ts index a3e5802..81435d0 100644 --- a/quartz-manager-frontend/src/app/services/index.ts +++ b/quartz-manager-frontend/src/app/services/index.ts @@ -3,9 +3,8 @@ export * from './user.service'; export * from './config.service'; export * from './auth.service'; export * from './scheduler.service'; -export * from './websocket.service'; -export * from './progress.websocket.service'; -export * from './logs.websocket.service'; +export * from './progress.rx-websocket.service'; +export * from './logs.rx-websocket.service'; export * from './trigger.service' export * from './job.service' diff --git a/quartz-manager-frontend/src/app/services/logs.websocket.service.ts b/quartz-manager-frontend/src/app/services/logs.websocket.service.ts deleted file mode 100644 index 97e77dd..0000000 --- a/quartz-manager-frontend/src/app/services/logs.websocket.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {Injectable} from '@angular/core'; -import {WebsocketService, ApiService, getBaseUrl, CONTEXT_PATH} from '.'; -import {SocketOption} from '../model/SocketOption.model'; - -@Injectable() -export class LogsWebsocketService extends WebsocketService { - - constructor(private apiService: ApiService) { - super(new SocketOption(getBaseUrl() + `${CONTEXT_PATH}/logs`, '/topic/logs', apiService.getToken)) - } - -} diff --git a/quartz-manager-frontend/src/app/services/progress.rx-websocket.service.ts b/quartz-manager-frontend/src/app/services/progress.rx-websocket.service.ts new file mode 100644 index 0000000..647f668 --- /dev/null +++ b/quartz-manager-frontend/src/app/services/progress.rx-websocket.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import {RxStompService} from './rx-stomp.service'; +import {ApiService} from './api.service'; +import SockJS from 'sockjs-client'; +import {CONTEXT_PATH, getBaseUrl} from './config.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ProgressRxWebsocketService extends RxStompService { + + constructor(private apiService: ApiService) { + super({ + webSocketFactory: () => new SockJS(`${getBaseUrl()}${CONTEXT_PATH}/progress?access_token=${this.apiService.getToken()}`), + heartbeatIncoming: 0, + heartbeatOutgoing: 20000, + reconnectDelay: 200, + debug: (msg: string): void => { + console.log(new Date(), msg); + } + }); + } +} diff --git a/quartz-manager-frontend/src/app/services/progress.websocket.service.ts b/quartz-manager-frontend/src/app/services/progress.websocket.service.ts deleted file mode 100644 index 7322e6f..0000000 --- a/quartz-manager-frontend/src/app/services/progress.websocket.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {Injectable} from '@angular/core'; -import {WebsocketService, ApiService, getBaseUrl, CONTEXT_PATH} from '.'; -import {SocketOption} from '../model/SocketOption.model'; - -@Injectable() -export class ProgressWebsocketService extends WebsocketService { - - constructor(private apiService: ApiService) { - super(new SocketOption(getBaseUrl() + `${CONTEXT_PATH}/progress`, '/topic/progress', apiService.getToken)) - } - -} diff --git a/quartz-manager-frontend/src/app/services/websocket.service.ts b/quartz-manager-frontend/src/app/services/websocket.service.ts deleted file mode 100644 index 11b4342..0000000 --- a/quartz-manager-frontend/src/app/services/websocket.service.ts +++ /dev/null @@ -1,136 +0,0 @@ -import {Observable, Subscriber} from 'rxjs'; -import {SocketEndpoint} from '../model/SocketEndpoint.model' - - -import Stomp from 'stompjs'; -import SockJS from 'sockjs-client'; -import {SocketOption} from '../model/SocketOption.model'; - -interface WebsocketSubscriber { - index: number, - observer: Subscriber -} - -export interface QuartzManagerWebsocketMessage { - type: string; - message: any; - headers: any; - self: boolean; -} - -export class WebsocketService { - - _options: SocketOption; - - _socket: SocketEndpoint = new SocketEndpoint(); - - observableStompConnection: Observable; - subscribers: Array = []; - subscriberIndex = 0; - - _messageIds: Array = []; - - reconnectionPromise: any; - - constructor(options: SocketOption) { - this._options = options - this.createObservableSocket(); - this.connect(); - } - - getOptions = () => { - } - - private createObservableSocket = () => { - this.observableStompConnection = new Observable((observer) => { - const subscriberIndex = this.subscriberIndex++; - this.addToSubscribers({index: subscriberIndex, observer}); - return () => this.removeFromSubscribers(subscriberIndex); - }); - } - - private addToSubscribers = (subscriber) => { - this.subscribers.push(subscriber); - } - - private removeFromSubscribers = (index) => { - this.subscribers = this.subscribers.filter(subscriber => subscriber.index !== index); - } - - getObservable = () => { - return this.observableStompConnection; - }; - - getMessage = function (data): QuartzManagerWebsocketMessage { - const out: QuartzManagerWebsocketMessage = {}; - out.type = 'SUCCESS'; - out.message = JSON.parse(data.body); - out.headers = {}; - out.headers.messageId = data.headers['message-id']; - - const messageIdIndex = this._messageIds.indexOf(out.headers.messageId); - if (messageIdIndex > -1) { - out.self = true; - this._messageIds = this._messageIds.splice(messageIdIndex, 1); - } - return out; - }; - - _socketListener = (frame) => { - console.log(`Connected to ${this._options.socketUrl}: ${frame}`); - this._socket.stomp.subscribe( - this._options.topicName, - data => this.subscribers.forEach(subscriber => subscriber.observer.next(this.getMessage(data))) - ); - } - - _onSocketError = (errorMsg) => { - const out: any = {}; - out.type = 'ERROR'; - out.message = errorMsg; - this.subscribers.forEach(subscriber => subscriber.observer.error(out)); - this.scheduleReconnection(); - } - - scheduleReconnection = () => { - this.reconnectionPromise = setTimeout(() => { - console.log(`Socket reconnecting to ${this._options.socketUrl}... (if it fails, next attempt in ${this._options.reconnectionTimeout} msec)`); - this.connect(); - }, this._options.reconnectionTimeout); - } - - reconnectNow = function () { - this._socket.stomp.disconnect(); - if (this.reconnectionPromise && this.reconnectionPromise.cancel) { - this.reconnectionPromise.cancel(); - } - this.connect(); - }; - - send = (message) => { - const id = Math.floor(Math.random() * 1000000); - this._socket.stomp.send(this._options.brokerName, { - priority: 9 - }, JSON.stringify({ - message: message, - id: id - })); - this._messageIds.push(id); - }; - - connect = () => { - const headers = {}; - - let socketUrl = this._options.socketUrl; - if (this._options.getAccessToken()) { - socketUrl += `?access_token=${this._options.getAccessToken()}`; - } - - this._socket.client = new SockJS(socketUrl); - this._socket.stomp = Stomp.over(this._socket.client); - this._socket.stomp.connect(headers, this._socketListener, this._onSocketError); - this._socket.stomp.onclose = this.scheduleReconnection; - } - - -} diff --git a/quartz-manager-frontend/src/app/views/manager/manager.component.html b/quartz-manager-frontend/src/app/views/manager/manager.component.html index c03d20c..cc71362 100644 --- a/quartz-manager-frontend/src/app/views/manager/manager.component.html +++ b/quartz-manager-frontend/src/app/views/manager/manager.component.html @@ -29,7 +29,10 @@
- + + diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java index 281d744..d616bf9 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketProgressNotifier.java @@ -21,7 +21,7 @@ public class WebSocketProgressNotifier implements WebhookSender Date: Tue, 20 Dec 2022 23:59:30 +0100 Subject: [PATCH 05/40] #103 migrated the progress websocket to rx-stomp --- quartz-manager-frontend/src/app/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/quartz-manager-frontend/src/app/components/index.ts b/quartz-manager-frontend/src/app/components/index.ts index 2a77c4a..dd226d3 100644 --- a/quartz-manager-frontend/src/app/components/index.ts +++ b/quartz-manager-frontend/src/app/components/index.ts @@ -5,3 +5,4 @@ export * from './logs-panel'; export * from './scheduler-control'; export * from './progress-panel'; export * from './trigger-list'; +export * from './simple-trigger-config'; From 7106dc0fbbb30ca8420f54c680e48d7a4399b4e1 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 21 Dec 2022 00:00:29 +0100 Subject: [PATCH 06/40] #103 clean up --- .../src/app/components/logs-panel/logs-panel.component.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts index ec76d25..ad390d2 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts @@ -1,12 +1,8 @@ -import {Component, OnInit, Input, Output, EventEmitter, OnDestroy} from '@angular/core'; +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {LogsWebsocketService, ApiService, getBaseUrl, CONTEXT_PATH, QuartzManagerWebsocketMessage} from '../../services'; -import {Observable} from 'rxjs'; -import {RxStompService,} from '../../services/rx-stomp.service'; -import {RxStompConfig} from '@stomp/rx-stomp/esm6/rx-stomp-config'; +import {ApiService} from '../../services'; import {LogsRxWebsocketService} from '../../services/logs.rx-websocket.service'; import {map} from 'rxjs/operators'; -import {Trigger} from '../../model/trigger.model'; import {TriggerKey} from '../../model/triggerKey.model'; From 727a11fcea55f34b0f8ac126390a37580bcdd5e2 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 21 Dec 2022 00:03:39 +0100 Subject: [PATCH 07/40] #103 fixed a test --- .../src/app/services/logs.rx-websocket.service.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts index 05dea34..c208cce 100644 --- a/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts +++ b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts @@ -1,12 +1,17 @@ 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'; describe('LogsRxWebsocketService', () => { let service: LogsRxWebsocketService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ApiService] + }); service = TestBed.inject(LogsRxWebsocketService); }); From 412e4559074b4724a18d9abc037886a538bc17da Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Fri, 2 Feb 2024 22:58:56 +0100 Subject: [PATCH 08/40] #103 step into accomodating the new trigger logic --- .../simple-trigger-config.component.spec.ts | 7 ++++++ .../simple-trigger-config.component.ts | 25 +++++++++++++++---- .../trigger-list/trigger-list.component.html | 4 ++- .../trigger-list/trigger-list.component.ts | 11 ++++---- .../app/views/manager/manager.component.ts | 3 ++- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts index 1f66f48..d186a80 100644 --- a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts +++ b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts @@ -222,6 +222,13 @@ describe('SimpleTriggerConfig', () => { const componentDe: DebugElement = fixture.debugElement; const submitButton = componentDe.query(By.css('form button[color="primary"]')); expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit'); + + expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull(); + + }); + + it('should reset the form when a new trigger is selected', () => { + }); it('should display the warning if there are no eligible jobs', () => { diff --git a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts index 15bd6db..000d7a4 100644 --- a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts +++ b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts @@ -34,9 +34,8 @@ export class SimpleTriggerConfigComponent implements OnInit { scheduler: Scheduler; - triggerLoading = true; + triggerLoading = false; - private fetchedTriggers = false; private triggerInProgress = false; private selectedTriggerKey: TriggerKey; @@ -64,6 +63,9 @@ export class SimpleTriggerConfigComponent implements OnInit { } openTriggerForm() { + // this.selectedTriggerKey = null; + // this.trigger = null; + // this.simpleTriggerReactiveForm.setValue(new SimpleTriggerReactiveForm()); this.enabledTriggerForm = true; } @@ -73,13 +75,26 @@ export class SimpleTriggerConfigComponent implements OnInit { @Input() set triggerKey(triggerKey: TriggerKey) { - if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name){ + if (!triggerKey) { + this.selectedTriggerKey = null; + this.trigger = null; + this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm()); + } else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) { + this._resetTheTrigger(); this.selectedTriggerKey = {...triggerKey} as TriggerKey; this.fetchSelectedTrigger(); } + this.openTriggerForm(); } + private _resetTheTrigger() { + this.trigger = null; + this.triggerInProgress = false; + this.selectedTriggerKey = null; + this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm()); + } + fetchSelectedTrigger = () => { this.triggerLoading = true; this.schedulerService.getSimpleTriggerConfig(this.selectedTriggerKey.name) @@ -105,13 +120,13 @@ export class SimpleTriggerConfigComponent implements OnInit { this.schedulerService.updateSimpleTriggerConfig : this.schedulerService.saveSimpleTriggerConfig; const simpleTriggerCommand = this._fromReactiveFormToCommand(); + this.triggerLoading = true; schedulerServiceCall(simpleTriggerCommand) .subscribe((retTrigger: SimpleTrigger) => { this.trigger = retTrigger; this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(retTrigger)); - this.fetchedTriggers = true; this.triggerInProgress = this.trigger.mayFireAgain; if (schedulerServiceCall === this.schedulerService.saveSimpleTriggerConfig) { @@ -121,7 +136,7 @@ export class SimpleTriggerConfigComponent implements OnInit { this.closeTriggerForm(); }, error => { this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger)); - }); + }, () => {this.triggerLoading = true}); } diff --git a/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.html b/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.html index 88677dd..7718dfb 100644 --- a/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.html +++ b/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.html @@ -9,7 +9,9 @@ + [ngClass]="{'selectedTrigger': selectedTrigger && selectedTrigger.name==triggerKey.name}" + (click)="selectTrigger(triggerKey)" + > {{ triggerKey.name }} diff --git a/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts b/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts index 14e784c..3a41163 100644 --- a/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts +++ b/quartz-manager-frontend/src/app/components/trigger-list/trigger-list.component.ts @@ -81,11 +81,12 @@ export class TriggerListComponent implements OnInit { } onNewTriggerBtnClicked() { - if (this.getTriggerKeyList() && this.getTriggerKeyList().length > 0) { - this.dialog.open(UnsupportedMultipleJobsDialog) - } else { - this.onNewTriggerClicked.emit(); - } + this.onNewTriggerClicked.emit(); + // if (this.getTriggerKeyList() && this.getTriggerKeyList().length > 0) { + // this.dialog.open(UnsupportedMultipleJobsDialog) + // } else { + // this.onNewTriggerClicked.emit(); + // } } onNewTrigger(newTrigger: SimpleTrigger) { diff --git a/quartz-manager-frontend/src/app/views/manager/manager.component.ts b/quartz-manager-frontend/src/app/views/manager/manager.component.ts index 01ce9f1..8c010dd 100644 --- a/quartz-manager-frontend/src/app/views/manager/manager.component.ts +++ b/quartz-manager-frontend/src/app/views/manager/manager.component.ts @@ -32,7 +32,8 @@ export class ManagerComponent implements OnInit { } onNewTriggerRequested() { - this.triggerConfigComponent.openTriggerForm(); + this.selectedTriggerKey = null; + // this.triggerConfigComponent.openTriggerForm(); } onNewTriggerCreated(newTrigger: SimpleTrigger) { From 5cb73019ded29fd81d31f4fb17bdfc4469a5819c Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 7 Feb 2024 00:19:17 +0100 Subject: [PATCH 09/40] bump version to 4.0.10-SNAPSHOT --- quartz-manager-parent/pom.xml | 12 ++++++------ quartz-manager-parent/quartz-manager-common/pom.xml | 2 +- .../quartz-manager-starter-api/pom.xml | 2 +- .../quartz-manager-starter-persistence/pom.xml | 2 +- .../quartz-manager-starter-security/pom.xml | 2 +- .../quartz-manager-starter-ui/pom.xml | 2 +- .../quartz-manager-web-showcase/pom.xml | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index b23625b..ac80760 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -10,7 +10,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT pom @@ -78,27 +78,27 @@ it.fabioformosa.quartz-manager quartz-manager-common - 4.0.9 + 4.0.10-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-api - 4.0.9 + 4.0.10-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-security - 4.0.9 + 4.0.10-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-persistence - 4.0.9 + 4.0.10-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-ui - 4.0.9 + 4.0.10-SNAPSHOT diff --git a/quartz-manager-parent/quartz-manager-common/pom.xml b/quartz-manager-parent/quartz-manager-common/pom.xml index 8e18ae9..6d9939d 100644 --- a/quartz-manager-parent/quartz-manager-common/pom.xml +++ b/quartz-manager-parent/quartz-manager-common/pom.xml @@ -3,7 +3,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT quartz-manager-common diff --git a/quartz-manager-parent/quartz-manager-starter-api/pom.xml b/quartz-manager-parent/quartz-manager-starter-api/pom.xml index 053f50b..02d4f16 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-api/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT quartz-manager-starter-api diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml index 52f3e03..bb2d3e6 100644 --- a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml @@ -3,7 +3,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT quartz-manager-starter-persistence diff --git a/quartz-manager-parent/quartz-manager-starter-security/pom.xml b/quartz-manager-parent/quartz-manager-starter-security/pom.xml index 53d5f71..3543422 100644 --- a/quartz-manager-parent/quartz-manager-starter-security/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-security/pom.xml @@ -4,7 +4,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT quartz-manager-starter-security diff --git a/quartz-manager-parent/quartz-manager-starter-ui/pom.xml b/quartz-manager-parent/quartz-manager-starter-ui/pom.xml index b4c0f64..a624d86 100644 --- a/quartz-manager-parent/quartz-manager-starter-ui/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-ui/pom.xml @@ -4,7 +4,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT quartz-manager-starter-ui diff --git a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml index 6e51702..123a045 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.9 + 4.0.10-SNAPSHOT quartz-manager-web-showcase From 63fbedbdc8efd42d2e6b935ba727f7e2100acffa Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Fri, 12 Jul 2024 00:36:47 +0200 Subject: [PATCH 10/40] moved the web-showcase from war to jar and added a dockerfile --- .dockerignore | 2 + Dockerfile | 34 +++++++++++ quartz-manager-parent/pom.xml | 6 ++ .../quartz-manager-web-showcase/pom.xml | 61 ++++++++----------- .../it/fabioformosa/ServletInitializer.java | 21 ------- 5 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile delete mode 100644 quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/ServletInitializer.java diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..03e9a08 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +# .dockerignore +quartz-manager-frontend/node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6105cbf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM maven:3.9.8-eclipse-temurin-21 AS build + +# Set the working directory +WORKDIR /app + +# Copy the pom.xml and download dependencies +COPY quartz-manager-parent/pom.xml ./quartz-manager-parent/ +COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/ +COPY quartz-manager-parent/quartz-manager-common ./quartz-manager-parent/quartz-manager-common +COPY quartz-manager-parent/quartz-manager-starter-api ./quartz-manager-parent/quartz-manager-starter-api +COPY quartz-manager-parent/quartz-manager-starter-persistence ./quartz-manager-parent/quartz-manager-starter-persistence +COPY quartz-manager-parent/quartz-manager-starter-security ./quartz-manager-parent/quartz-manager-starter-security +COPY quartz-manager-parent/quartz-manager-starter-ui ./quartz-manager-parent/quartz-manager-starter-ui +COPY quartz-manager-parent/quartz-manager-web-showcase ./quartz-manager-parent/quartz-manager-web-showcase +COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/ +COPY quartz-manager-frontend ./quartz-manager-frontend +WORKDIR /app/quartz-manager-parent +RUN mvn clean package -DskipTests -P=build-webjar + + +# Stage 2: Create the final image +FROM openjdk:11-jre-slim + +# Set the working directory +WORKDIR /app + +# Copy the JAR file from the build stage +COPY --from=build /app/quartz-manager-parent/quartz-manager-web-showcase/target/*.jar app.jar + +# Expose the application port +EXPOSE 8080 + +# Run the application +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index ac80760..325edf6 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -43,6 +43,7 @@ 9 UTF-8 + 1.18.30 2.22.0 2.22.0 0.8.8 @@ -100,6 +101,11 @@ quartz-manager-starter-ui 4.0.10-SNAPSHOT + + org.projectlombok + lombok + ${org.projectlombok.version} + diff --git a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml index 123a045..7a10254 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml @@ -8,9 +8,9 @@ 4.0.10-SNAPSHOT - quartz-manager-web-showcase + jar - war + quartz-manager-web-showcase Quartz Manager Web Showcase A webapp that imports Quartz Manager API lib and the frontend webjar @@ -49,15 +49,10 @@ org.springframework.boot spring-boot-devtools - - org.springframework.boot - spring-boot-configuration-processor - true - - + org.springframework.boot - spring-boot-starter-tomcat - provided + spring-boot-configuration-processor + true org.springframework.boot @@ -123,30 +118,28 @@ - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.0 - - 9 - 9 - - - - + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 9 + 9 + + + diff --git a/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/ServletInitializer.java b/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/ServletInitializer.java deleted file mode 100644 index dcc3414..0000000 --- a/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/ServletInitializer.java +++ /dev/null @@ -1,21 +0,0 @@ -package it.fabioformosa; - -import lombok.Generated; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; - -/** - * ServletInitializer needs to deploy quartz-manager into a servlet container as a war file - * - * @author Fabio Formosa - * - */ -@Generated -public class ServletInitializer extends SpringBootServletInitializer { - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - return application.sources(QuartzManagerDemoApplication.class); - } - -} From e6927209e546bfae369456ef6e074bd4a499b506 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 13 Jul 2024 13:00:12 +0200 Subject: [PATCH 11/40] added a helm chart to the web-showcase module and cloudbuild.yaml and skaffold.yaml --- cloudbuild.yaml | 20 +++++ .../helm/Chart.yaml | 24 ++++++ .../helm/templates/NOTES.txt | 22 +++++ .../helm/templates/_helpers.yaml | 62 ++++++++++++++ .../helm/templates/configmap.yaml | 9 ++ .../helm/templates/deployment.yaml | 68 +++++++++++++++ .../helm/templates/hpa.yaml | 28 +++++++ .../helm/templates/ingress.yaml | 61 ++++++++++++++ .../helm/templates/service.yaml | 15 ++++ .../helm/templates/serviceaccount.yaml | 12 +++ .../helm/values.yaml | 83 +++++++++++++++++++ 11 files changed, 404 insertions(+) create mode 100644 cloudbuild.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/NOTES.txt create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/_helpers.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/configmap.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/deployment.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/hpa.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/ingress.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/service.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/templates/serviceaccount.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..67121bc --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,20 @@ +substitutions: + _REGION: europe-west8 +steps: + + # Step 1: Docker build&push + - name: 'gcr.io/k8s-skaffold/skaffold' + entrypoint: 'sh' + args: + - -xe + - -c + - | + # Build and push images + sed -i s/_IMAGE_TAG_POLICY/$SHORT_SHA/g skaffold.yaml + sed -i s/_HELM_CHART_VERSION/0.0.0/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml + sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/quartzmanager-standalone/Chart.yaml + sed -i s/_HELM_CHART_NAME/quartzmanager-standalone/g skaffold.yaml + sed -i s/_HELM_NAMESPACE/quartzmanager/g skaffold.yaml + skaffold build --file-output=/workspace/artifacts.json \ + --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ + --push=true diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml new file mode 100644 index 0000000..d7d2b59 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: quartz-manager-standalone +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: _HELM_CHART_VERSION + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "_HELM_APP_VERSION" diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/NOTES.txt b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/NOTES.txt new file mode 100644 index 0000000..d432c5c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "quartzmanager-standalone.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "quartzmanager-standalone.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "quartzmanager-standalone.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "quartzmanager-standalone.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/_helpers.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/_helpers.yaml new file mode 100644 index 0000000..59d7d95 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/_helpers.yaml @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "quartzmanager-standalone.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "quartzmanager-standalone.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "quartzmanager-standalone.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "quartzmanager-standalone.labels" -}} +helm.sh/chart: {{ include "quartzmanager-standalone.chart" . }} +{{ include "quartzmanager-standalone.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "quartzmanager-standalone.selectorLabels" -}} +app.kubernetes.io/name: {{ include "quartzmanager-standalone.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "quartzmanager-standalone.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "quartzmanager-standalone.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/configmap.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/configmap.yaml new file mode 100644 index 0000000..aa87e0a --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: "v1" +kind: "ConfigMap" +metadata: + name: {{ include "quartzmanager-standalone.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Chart.Name }} +data: + envName: {{ .Values.config.envName | quote }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/deployment.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/deployment.yaml new file mode 100644 index 0000000..760aeea --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "quartzmanager-standalone.fullname" . }} + labels: + {{- include "quartzmanager-standalone.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "quartzmanager-standalone.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "quartzmanager-standalone.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "quartzmanager-standalone.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP +{{/* livenessProbe:*/}} +{{/* initialDelaySeconds: 30*/}} +{{/* timeoutSeconds: 10*/}} +{{/* httpGet:*/}} +{{/* path: /*/}} +{{/* port: 8080*/}} +{{/* readinessProbe:*/}} +{{/* initialDelaySeconds: 30*/}} +{{/* timeoutSeconds: 10*/}} +{{/* httpGet:*/}} +{{/* path: /*/}} +{{/* port: 8080*/}} + resources: + {{- toYaml .Values.resources | nindent 12 }} + envFrom: + - configMapRef: + name: {{ include "quartzmanager-standalone.fullname" . }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/hpa.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/hpa.yaml new file mode 100644 index 0000000..67973b7 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "quartzmanager-standalone.fullname" . }} + labels: + {{- include "quartzmanager-standalone.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "quartzmanager-standalone.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/ingress.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/ingress.yaml new file mode 100644 index 0000000..46de18b --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "quartzmanager-standalone.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "quartzmanager-standalone.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/service.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/service.yaml new file mode 100644 index 0000000..96bcb76 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "quartzmanager-standalone.fullname" . }} + labels: + {{- include "quartzmanager-standalone.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "quartzmanager-standalone.selectorLabels" . | nindent 4 }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/serviceaccount.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/serviceaccount.yaml new file mode 100644 index 0000000..6e11e89 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "quartzmanager-standalone.serviceAccountName" . }} + labels: + {{- include "quartzmanager-standalone.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml new file mode 100644 index 0000000..8223641 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml @@ -0,0 +1,83 @@ +# Default values for hello-world. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: "europe-west8-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: false + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} +# fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true +# runAsNonRoot: true +# runAsUser: 1000 + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: +# cpu: 100m +# memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +config: + envName: NA From 9f46e5256403d0b90e4deb4e8d42c0d0064350c9 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 13 Jul 2024 15:28:24 +0200 Subject: [PATCH 12/40] added a logging policy to cloudbuild.yaml and pushed skaffold.yaml --- cloudbuild.yaml | 3 +++ skaffold.yaml | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 skaffold.yaml diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 67121bc..449efa2 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -18,3 +18,6 @@ steps: skaffold build --file-output=/workspace/artifacts.json \ --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ --push=true + +options: + logging: CLOUD_LOGGING_ONLY diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 0000000..241e1b4 --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,9 @@ +apiVersion: skaffold/v4beta7 +kind: Config +build: + tagPolicy: + envTemplate: + template: "_IMAGE_TAG_POLICY" + artifacts: + - image: quartz-manager-standalone + context: ./ From abd25d61588eee1c03ac1ed25e7a16e8d5363817 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 13 Jul 2024 15:33:10 +0200 Subject: [PATCH 13/40] fixed a wrong path in cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 449efa2..da6117b 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -12,7 +12,7 @@ steps: # Build and push images sed -i s/_IMAGE_TAG_POLICY/$SHORT_SHA/g skaffold.yaml sed -i s/_HELM_CHART_VERSION/0.0.0/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml - sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/quartzmanager-standalone/Chart.yaml + sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml sed -i s/_HELM_CHART_NAME/quartzmanager-standalone/g skaffold.yaml sed -i s/_HELM_NAMESPACE/quartzmanager/g skaffold.yaml skaffold build --file-output=/workspace/artifacts.json \ From 7a742d5aeada738b73f59195538b6d3b3538b5fe Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 13 Jul 2024 15:52:40 +0200 Subject: [PATCH 14/40] fixed copy path in the Dockerfile --- Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6105cbf..7058a04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,14 +6,14 @@ WORKDIR /app # Copy the pom.xml and download dependencies COPY quartz-manager-parent/pom.xml ./quartz-manager-parent/ COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/ -COPY quartz-manager-parent/quartz-manager-common ./quartz-manager-parent/quartz-manager-common -COPY quartz-manager-parent/quartz-manager-starter-api ./quartz-manager-parent/quartz-manager-starter-api -COPY quartz-manager-parent/quartz-manager-starter-persistence ./quartz-manager-parent/quartz-manager-starter-persistence -COPY quartz-manager-parent/quartz-manager-starter-security ./quartz-manager-parent/quartz-manager-starter-security -COPY quartz-manager-parent/quartz-manager-starter-ui ./quartz-manager-parent/quartz-manager-starter-ui -COPY quartz-manager-parent/quartz-manager-web-showcase ./quartz-manager-parent/quartz-manager-web-showcase +COPY quartz-manager-parent/quartz-manager-common ./quartz-manager-parent/quartz-manager-common/ +COPY quartz-manager-parent/quartz-manager-starter-api ./quartz-manager-parent/quartz-manager-starter-api/ +COPY quartz-manager-parent/quartz-manager-starter-persistence ./quartz-manager-parent/quartz-manager-starter-persistence/ +COPY quartz-manager-parent/quartz-manager-starter-security ./quartz-manager-parent/quartz-manager-starter-security/ +COPY quartz-manager-parent/quartz-manager-starter-ui ./quartz-manager-parent/quartz-manager-starter-ui/ +COPY quartz-manager-parent/quartz-manager-web-showcase ./quartz-manager-parent/quartz-manager-web-showcase/ COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/ -COPY quartz-manager-frontend ./quartz-manager-frontend +COPY quartz-manager-frontend ./quartz-manager-frontend/ WORKDIR /app/quartz-manager-parent RUN mvn clean package -DskipTests -P=build-webjar From 8ba33f25b4891c828232e1782fbab81647886863 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 13 Jul 2024 16:00:44 +0200 Subject: [PATCH 15/40] fixed copy path in the Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7058a04..64c6968 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ FROM openjdk:11-jre-slim WORKDIR /app # Copy the JAR file from the build stage -COPY --from=build /app/quartz-manager-parent/quartz-manager-web-showcase/target/*.jar app.jar +COPY --from=build /app/quartz-manager-parent/quartz-manager-web-showcase/target/*-SNAPSHOT.jar app.jar # Expose the application port EXPOSE 8080 From 4d5e8f62c3dce6959d19740ce1c8712c937adc98 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Mon, 15 Jul 2024 23:39:51 +0200 Subject: [PATCH 16/40] added a google deploy pipeline --- cloudbuild.yaml | 23 ++++++++++++++++++- .../deploy/dev.yaml | 9 ++++++++ .../deploy/pipeline.yaml | 12 ++++++++++ skaffold.yaml | 13 +++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml diff --git a/cloudbuild.yaml b/cloudbuild.yaml index da6117b..a7fedb9 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -14,10 +14,31 @@ steps: sed -i s/_HELM_CHART_VERSION/0.0.0/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml sed -i s/_HELM_CHART_NAME/quartzmanager-standalone/g skaffold.yaml - sed -i s/_HELM_NAMESPACE/quartzmanager/g skaffold.yaml + sed -i s/_HELM_NAMESPACE/quartzmanager-dev/g skaffold.yaml skaffold build --file-output=/workspace/artifacts.json \ --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ --push=true + # Step 2: deploy + - name: 'google/cloud-sdk:latest' + entrypoint: 'sh' + args: + - -xe + - -c + - | + gcloud config set deploy/region ${_REGION} + gcloud deploy apply --file deploy/pipeline.yaml + gcloud deploy apply --file deploy/dev.yaml + gcloud deploy releases create rel-${SHORT_SHA} \ + --delivery-pipeline quartz-manager-standalone-pipeline \ + --description "$(git log -1 --pretty='%s')" \ + --build-artifacts /workspace/artifacts.json \ + --annotations "commit_ui=https://source.cloud.google.com/$PROJECT_ID/quartz-manager-standalone/+/$COMMIT_SHA" +artifacts: + objects: + location: 'gs://$PROJECT_ID-gcdeploy-artifacts/' + paths: + - '/workspace/artifacts.json' + options: logging: CLOUD_LOGGING_ONLY diff --git a/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml b/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml new file mode 100644 index 0000000..b401d80 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml @@ -0,0 +1,9 @@ +apiVersion: deploy.cloud.google.com/v1 +kind: Target +metadata: + name: dev + annotations: {} + labels: {} +description: dev +gke: + cluster: projects/quartz-manager-test/locations/europe-west8/clusters/gke-cluster diff --git a/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml b/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml new file mode 100644 index 0000000..627993d --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml @@ -0,0 +1,12 @@ +apiVersion: deploy.cloud.google.com/v1 +kind: DeliveryPipeline +metadata: + name: quartz-manager-pipeline + labels: + app: quartz-manager-standalone +description: quartz-manager-standalone delivery pipeline +serialPipeline: + stages: + - targetId: dev + profiles: + - dev diff --git a/skaffold.yaml b/skaffold.yaml index 241e1b4..1142ff6 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -7,3 +7,16 @@ build: artifacts: - image: quartz-manager-standalone context: ./ +profiles: + - name: dev + deploy: + helm: + releases: + - name: _HELM_CHART_NAME + createNamespace: true + namespace: _HELM_NAMESPACE + chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm/hello-world +# valuesFiles: +# - helm/envs/dev/values.yaml + setValueTemplates: + image.tag: "_IMAGE_TAG_POLICY" From 6d36e4620c6855094a3d3ea6982e65e32a1226f9 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Mon, 15 Jul 2024 23:47:32 +0200 Subject: [PATCH 17/40] fixed the path of the pipeline file --- cloudbuild.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index a7fedb9..8ec8a36 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -27,8 +27,8 @@ steps: - -c - | gcloud config set deploy/region ${_REGION} - gcloud deploy apply --file deploy/pipeline.yaml - gcloud deploy apply --file deploy/dev.yaml + gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml + gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml gcloud deploy releases create rel-${SHORT_SHA} \ --delivery-pipeline quartz-manager-standalone-pipeline \ --description "$(git log -1 --pretty='%s')" \ From 9cc55492dc1241724ba9159fbce207115e657a00 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Mon, 15 Jul 2024 23:58:08 +0200 Subject: [PATCH 18/40] fixed the pipeline name --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 8ec8a36..50c49f8 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -30,7 +30,7 @@ steps: gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml gcloud deploy releases create rel-${SHORT_SHA} \ - --delivery-pipeline quartz-manager-standalone-pipeline \ + --delivery-pipeline quartz-manager-pipeline \ --description "$(git log -1 --pretty='%s')" \ --build-artifacts /workspace/artifacts.json \ --annotations "commit_ui=https://source.cloud.google.com/$PROJECT_ID/quartz-manager-standalone/+/$COMMIT_SHA" From f71c9b20ab1e458ae1f96353631a5883d8d7aa0d Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 16 Jul 2024 00:15:05 +0200 Subject: [PATCH 19/40] minor commit --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 50c49f8..b620e26 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -19,7 +19,7 @@ steps: --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ --push=true - # Step 2: deploy + # Step 2: cloud deploy - name: 'google/cloud-sdk:latest' entrypoint: 'sh' args: From a5750d1d0d2224759a1cd36bf7b95709235c4bd9 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 16 Jul 2024 00:24:10 +0200 Subject: [PATCH 20/40] duplicated the helm props replacements --- cloudbuild.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index b620e26..17695a7 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -26,6 +26,10 @@ steps: - -xe - -c - | + sed -i s/_HELM_CHART_VERSION/0.0.0/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml + sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml + sed -i s/_HELM_CHART_NAME/quartzmanager-standalone/g skaffold.yaml + sed -i s/_HELM_NAMESPACE/quartzmanager-dev/g skaffold.yaml gcloud config set deploy/region ${_REGION} gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml From 226296737dd68b7f6c44d07019b98839a897df33 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 16 Jul 2024 23:05:34 +0200 Subject: [PATCH 21/40] added verbosity to cloudbuild.yaml --- cloudbuild.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 17695a7..6ffec9f 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -37,6 +37,7 @@ steps: --delivery-pipeline quartz-manager-pipeline \ --description "$(git log -1 --pretty='%s')" \ --build-artifacts /workspace/artifacts.json \ + --verbosity \ --annotations "commit_ui=https://source.cloud.google.com/$PROJECT_ID/quartz-manager-standalone/+/$COMMIT_SHA" artifacts: objects: From b1ff70265fbaa226b9e1bdbf7be4e7ace54a2db9 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 16 Jul 2024 23:15:54 +0200 Subject: [PATCH 22/40] added verbosity to cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 6ffec9f..18c2a43 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -37,7 +37,7 @@ steps: --delivery-pipeline quartz-manager-pipeline \ --description "$(git log -1 --pretty='%s')" \ --build-artifacts /workspace/artifacts.json \ - --verbosity \ + --verbosity=debug \ --annotations "commit_ui=https://source.cloud.google.com/$PROJECT_ID/quartz-manager-standalone/+/$COMMIT_SHA" artifacts: objects: From 307c6eab98864bd440e52c1237e6fd619fac998b Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Tue, 16 Jul 2024 23:49:38 +0200 Subject: [PATCH 23/40] fixed the docker image in the cloud deploy pipeline --- .../quartz-manager-web-showcase/helm/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml index 8223641..dab99e5 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/values.yaml @@ -5,7 +5,7 @@ replicaCount: 1 image: - repository: "europe-west8-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone" + repository: "europe-west8-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone/quartz-manager-standalone" pullPolicy: IfNotPresent imagePullSecrets: [] From 3b325536e8e81fafd50ccb1f27ec2d07caa69e48 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 17 Jul 2024 00:06:58 +0200 Subject: [PATCH 24/40] fixed the dev cluster reference --- .../quartz-manager-web-showcase/deploy/dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml b/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml index b401d80..1883818 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml +++ b/quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml @@ -6,4 +6,4 @@ metadata: labels: {} description: dev gke: - cluster: projects/quartz-manager-test/locations/europe-west8/clusters/gke-cluster + cluster: projects/quartz-manager-test/locations/europe-west8-a/clusters/gke-cluster From 0a4a31ae655ec83d3c9515e7a399481a817c642c Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 17 Jul 2024 00:21:24 +0200 Subject: [PATCH 25/40] fixed the chart path --- skaffold.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skaffold.yaml b/skaffold.yaml index 1142ff6..8b62f5c 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -15,7 +15,7 @@ profiles: - name: _HELM_CHART_NAME createNamespace: true namespace: _HELM_NAMESPACE - chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm/hello-world + chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm # valuesFiles: # - helm/envs/dev/values.yaml setValueTemplates: From 82ac821b34dd69580d62701a90b83e98c498cfc3 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 17 Jul 2024 00:30:49 +0200 Subject: [PATCH 26/40] minor commit --- .../quartz-manager-web-showcase/helm/Chart.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml index d7d2b59..6f253a5 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml @@ -3,7 +3,6 @@ name: quartz-manager-standalone description: A Helm chart for Kubernetes # A chart can be either an 'application' or a 'library' chart. -# # Application charts are a collection of templates that can be packaged into versioned archives # to be deployed. # From 4537c8e304e3c47415be2c16028c354ccc38c0d9 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Thu, 18 Jul 2024 23:53:27 +0200 Subject: [PATCH 27/40] minor commit --- .../quartz-manager-web-showcase/helm/Chart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml index 6f253a5..a49259a 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml @@ -2,6 +2,7 @@ apiVersion: v2 name: quartz-manager-standalone description: A Helm chart for Kubernetes + # A chart can be either an 'application' or a 'library' chart. # Application charts are a collection of templates that can be packaged into versioned archives # to be deployed. From e5a6b8b32b9101e175f3447f795efc541a3a8267 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 27 Jul 2024 16:33:24 +0200 Subject: [PATCH 28/40] minor commit --- cloudbuild.yaml | 4 ---- .../quartz-manager-web-showcase/helm/Chart.yaml | 2 +- skaffold.yaml | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 18c2a43..f92a6c0 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -11,9 +11,7 @@ steps: - | # Build and push images sed -i s/_IMAGE_TAG_POLICY/$SHORT_SHA/g skaffold.yaml - sed -i s/_HELM_CHART_VERSION/0.0.0/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml - sed -i s/_HELM_CHART_NAME/quartzmanager-standalone/g skaffold.yaml sed -i s/_HELM_NAMESPACE/quartzmanager-dev/g skaffold.yaml skaffold build --file-output=/workspace/artifacts.json \ --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ @@ -26,9 +24,7 @@ steps: - -xe - -c - | - sed -i s/_HELM_CHART_VERSION/0.0.0/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml - sed -i s/_HELM_CHART_NAME/quartzmanager-standalone/g skaffold.yaml sed -i s/_HELM_NAMESPACE/quartzmanager-dev/g skaffold.yaml gcloud config set deploy/region ${_REGION} gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml diff --git a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml index a49259a..5a63105 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml +++ b/quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: _HELM_CHART_VERSION +version: 1.0.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/skaffold.yaml b/skaffold.yaml index 8b62f5c..8970eb8 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -12,7 +12,7 @@ profiles: deploy: helm: releases: - - name: _HELM_CHART_NAME + - name: quartzmanager-standalone createNamespace: true namespace: _HELM_NAMESPACE chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm From 9a0789cab0f22eaca1f852d8c9ceb3ca9e97bf05 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 27 Jul 2024 17:08:11 +0200 Subject: [PATCH 29/40] minor commit --- cloudbuild.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index f92a6c0..c9c76b6 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,7 +2,7 @@ substitutions: _REGION: europe-west8 steps: - # Step 1: Docker build&push + # Step 1: Google Cloud Build - Docker build&push - name: 'gcr.io/k8s-skaffold/skaffold' entrypoint: 'sh' args: @@ -17,7 +17,7 @@ steps: --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ --push=true - # Step 2: cloud deploy + # Step 2: Google Cloud Deploy - deploy - name: 'google/cloud-sdk:latest' entrypoint: 'sh' args: From 9d2a01ebbec8ffb628f30ae1a6454254b9a6fb56 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 27 Jul 2024 17:33:31 +0200 Subject: [PATCH 30/40] added the staging and prod env to the delivery pipeline --- cloudbuild.yaml | 2 -- .../deploy/pipeline.yaml | 6 +++++ .../deploy/prod.yaml | 10 +++++++ .../deploy/staging.yaml | 10 +++++++ skaffold.yaml | 26 ++++++++++++++++++- 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml create mode 100644 quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml diff --git a/cloudbuild.yaml b/cloudbuild.yaml index c9c76b6..54ec404 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -12,7 +12,6 @@ steps: # Build and push images sed -i s/_IMAGE_TAG_POLICY/$SHORT_SHA/g skaffold.yaml sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml - sed -i s/_HELM_NAMESPACE/quartzmanager-dev/g skaffold.yaml skaffold build --file-output=/workspace/artifacts.json \ --default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \ --push=true @@ -25,7 +24,6 @@ steps: - -c - | sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml - sed -i s/_HELM_NAMESPACE/quartzmanager-dev/g skaffold.yaml gcloud config set deploy/region ${_REGION} gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml diff --git a/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml b/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml index 627993d..3d3c7fa 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml +++ b/quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml @@ -10,3 +10,9 @@ serialPipeline: - targetId: dev profiles: - dev + - targetId: staging + profiles: + - staging + - targetId: prod + profiles: + - prod diff --git a/quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml b/quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml new file mode 100644 index 0000000..ea621a6 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml @@ -0,0 +1,10 @@ +apiVersion: deploy.cloud.google.com/v1 +kind: Target +metadata: + name: prod + annotations: {} + labels: {} +description: prod +requireApproval: true +gke: + cluster: projects/quartz-manager-test/locations/europe-west8-a/clusters/gke-cluster diff --git a/quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml b/quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml new file mode 100644 index 0000000..ecaf3a7 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml @@ -0,0 +1,10 @@ +apiVersion: deploy.cloud.google.com/v1 +kind: Target +metadata: + name: staging + annotations: {} + labels: {} +description: staging +requireApproval: true +gke: + cluster: projects/quartz-manager-test/locations/europe-west8-a/clusters/gke-cluster diff --git a/skaffold.yaml b/skaffold.yaml index 8970eb8..035e751 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -14,9 +14,33 @@ profiles: releases: - name: quartzmanager-standalone createNamespace: true - namespace: _HELM_NAMESPACE + namespace: quartzmanager-dev chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm # valuesFiles: # - helm/envs/dev/values.yaml setValueTemplates: image.tag: "_IMAGE_TAG_POLICY" + - name: staging + deploy: + helm: + releases: + - name: quartzmanager-standalone + createNamespace: true + namespace: quartzmanager-staging + chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm + # valuesFiles: + # - helm/envs/dev/values.yaml + setValueTemplates: + image.tag: "_IMAGE_TAG_POLICY" + - name: prod + deploy: + helm: + releases: + - name: quartzmanager-standalone + createNamespace: true + namespace: quartzmanager-prod + chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm + # valuesFiles: + # - helm/envs/dev/values.yaml + setValueTemplates: + image.tag: "_IMAGE_TAG_POLICY" From fab977fd7d1996403678b362d97ef3d4092b39fe Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Sat, 27 Jul 2024 17:41:31 +0200 Subject: [PATCH 31/40] added the staging and prod env to the delivery pipeline --- cloudbuild.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 54ec404..bfa16d0 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -27,6 +27,8 @@ steps: gcloud config set deploy/region ${_REGION} gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml + gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml + gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml gcloud deploy releases create rel-${SHORT_SHA} \ --delivery-pipeline quartz-manager-pipeline \ --description "$(git log -1 --pretty='%s')" \ From dcbf3eb277c678ed9ef233293df9d022175c83cb Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Fri, 23 May 2025 20:10:53 +0200 Subject: [PATCH 32/40] migrated to the new maven central repo --- quartz-manager-parent/pom.xml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index 325edf6..a7be0db 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -246,26 +246,25 @@ - ossrh - https://oss.sonatype.org/content/repositories/snapshots + maven-central-release + https://central.sonatype.com/repository/maven-snapshots/ - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - + maven-central-release + https://central.sonatype.com - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - true + maven-central-release + true + published From 95769248a32a5d976cc33738f1c242d07284d2a4 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Fri, 23 May 2025 21:52:30 +0200 Subject: [PATCH 33/40] migrated to the new maven central repo --- .github/workflows/maven-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml index cdf26e4..33d4a9a 100644 --- a/.github/workflows/maven-release.yml +++ b/.github/workflows/maven-release.yml @@ -19,7 +19,7 @@ jobs: with: java-version: '11' distribution: 'temurin' - server-id: ossrh + server-id: maven-central-release server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }} @@ -31,8 +31,8 @@ jobs: - name: Publish to maven central run: mvn deploy --file quartz-manager-parent/pom.xml --batch-mode -P "release-maven-central,build-webjar" env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} - name: Set up Java 11 for publishing to GitHub Packages From 56d9f5d94fb26b48a0572d9de538ae56d5d45e97 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Fri, 23 May 2025 23:04:28 +0200 Subject: [PATCH 34/40] bump to version 4.0.10 --- CHANGELOG.md | 3 +++ quartz-manager-parent/pom.xml | 12 ++++++------ quartz-manager-parent/quartz-manager-common/pom.xml | 2 +- .../quartz-manager-starter-api/pom.xml | 2 +- .../quartz-manager-starter-persistence/pom.xml | 2 +- .../quartz-manager-starter-security/pom.xml | 2 +- .../quartz-manager-starter-ui/pom.xml | 2 +- .../quartz-manager-web-showcase/pom.xml | 2 +- 8 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2093f93..832fcc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## **v4.0.10** +Migrated to the new maven central repo + ## **v4.0.9** Fixed a bug which prevented to run the liquibase migration scripts in case of usage of quartz-manager-starter-persistence diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index a7be0db..c61b3b3 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -10,7 +10,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 pom @@ -79,27 +79,27 @@ it.fabioformosa.quartz-manager quartz-manager-common - 4.0.10-SNAPSHOT + 4 it.fabioformosa.quartz-manager quartz-manager-starter-api - 4.0.10-SNAPSHOT + 4 it.fabioformosa.quartz-manager quartz-manager-starter-security - 4.0.10-SNAPSHOT + 4 it.fabioformosa.quartz-manager quartz-manager-starter-persistence - 4.0.10-SNAPSHOT + 4 it.fabioformosa.quartz-manager quartz-manager-starter-ui - 4.0.10-SNAPSHOT + 4 org.projectlombok diff --git a/quartz-manager-parent/quartz-manager-common/pom.xml b/quartz-manager-parent/quartz-manager-common/pom.xml index 6d9939d..4d2a6dd 100644 --- a/quartz-manager-parent/quartz-manager-common/pom.xml +++ b/quartz-manager-parent/quartz-manager-common/pom.xml @@ -3,7 +3,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 quartz-manager-common diff --git a/quartz-manager-parent/quartz-manager-starter-api/pom.xml b/quartz-manager-parent/quartz-manager-starter-api/pom.xml index 02d4f16..5db9616 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-api/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 quartz-manager-starter-api diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml index bb2d3e6..f2bcba3 100644 --- a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml @@ -3,7 +3,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 quartz-manager-starter-persistence diff --git a/quartz-manager-parent/quartz-manager-starter-security/pom.xml b/quartz-manager-parent/quartz-manager-starter-security/pom.xml index 3543422..aaaec43 100644 --- a/quartz-manager-parent/quartz-manager-starter-security/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-security/pom.xml @@ -4,7 +4,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 quartz-manager-starter-security diff --git a/quartz-manager-parent/quartz-manager-starter-ui/pom.xml b/quartz-manager-parent/quartz-manager-starter-ui/pom.xml index a624d86..d4fbcdd 100644 --- a/quartz-manager-parent/quartz-manager-starter-ui/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-ui/pom.xml @@ -4,7 +4,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 quartz-manager-starter-ui diff --git a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml index 7a10254..915f35f 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4.0.10-SNAPSHOT + 4 jar From c198d32bd5f011abba3d29a3c37683aceed9791b Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Fri, 23 May 2025 23:06:34 +0200 Subject: [PATCH 35/40] bump to version 4.0.11-SNAPSHOT --- quartz-manager-parent/pom.xml | 12 ++++++------ quartz-manager-parent/quartz-manager-common/pom.xml | 2 +- .../quartz-manager-starter-api/pom.xml | 2 +- .../quartz-manager-starter-persistence/pom.xml | 2 +- .../quartz-manager-starter-security/pom.xml | 2 +- .../quartz-manager-starter-ui/pom.xml | 2 +- .../quartz-manager-web-showcase/pom.xml | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index c61b3b3..491f57c 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -10,7 +10,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT pom @@ -79,27 +79,27 @@ it.fabioformosa.quartz-manager quartz-manager-common - 4 + 4.0.11-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-api - 4 + 4.0.11-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-security - 4 + 4.0.11-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-persistence - 4 + 4.0.11-SNAPSHOT it.fabioformosa.quartz-manager quartz-manager-starter-ui - 4 + 4.0.11-SNAPSHOT org.projectlombok diff --git a/quartz-manager-parent/quartz-manager-common/pom.xml b/quartz-manager-parent/quartz-manager-common/pom.xml index 4d2a6dd..2eec18c 100644 --- a/quartz-manager-parent/quartz-manager-common/pom.xml +++ b/quartz-manager-parent/quartz-manager-common/pom.xml @@ -3,7 +3,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT quartz-manager-common diff --git a/quartz-manager-parent/quartz-manager-starter-api/pom.xml b/quartz-manager-parent/quartz-manager-starter-api/pom.xml index 5db9616..557bc03 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-api/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT quartz-manager-starter-api diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml index f2bcba3..20811ef 100644 --- a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml @@ -3,7 +3,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT quartz-manager-starter-persistence diff --git a/quartz-manager-parent/quartz-manager-starter-security/pom.xml b/quartz-manager-parent/quartz-manager-starter-security/pom.xml index aaaec43..f69ee60 100644 --- a/quartz-manager-parent/quartz-manager-starter-security/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-security/pom.xml @@ -4,7 +4,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT quartz-manager-starter-security diff --git a/quartz-manager-parent/quartz-manager-starter-ui/pom.xml b/quartz-manager-parent/quartz-manager-starter-ui/pom.xml index d4fbcdd..f72cd7f 100644 --- a/quartz-manager-parent/quartz-manager-starter-ui/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-ui/pom.xml @@ -4,7 +4,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT quartz-manager-starter-ui diff --git a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml index 915f35f..0d80c5a 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml @@ -5,7 +5,7 @@ it.fabioformosa.quartz-manager quartz-manager-parent - 4 + 4.0.11-SNAPSHOT jar From 902542e480f622a19ef849f2a23cf0a4d192f177 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Thu, 23 Apr 2026 22:39:34 +0200 Subject: [PATCH 36/40] Update sonar-java.yml added the workflow_dispatch trigger for the sonar action --- .github/workflows/sonar-java.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sonar-java.yml b/.github/workflows/sonar-java.yml index 6d1c130..cf43625 100644 --- a/.github/workflows/sonar-java.yml +++ b/.github/workflows/sonar-java.yml @@ -7,6 +7,7 @@ on: # paths: [ 'quartz-manager-parent/**' ] pull_request: types: [opened, synchronize, reopened] + workflow_dispatch: jobs: build: name: Build and analyze From 31658416f504670fe355802ff0523456819459c2 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 6 May 2026 01:22:30 +0200 Subject: [PATCH 37/40] #103 final step into multi-trigger feature --- quartz-manager-frontend/.eslintrc.sonar.json | 19 ++++++ quartz-manager-frontend/package-lock.json | 20 ++++++ quartz-manager-frontend/package.json | 3 + quartz-manager-frontend/src/_test.ts | 4 +- .../logs-panel/logs-panel.component.spec.ts | 64 +++++++++++++++++++ .../logs-panel/logs-panel.component.ts | 49 +++++++------- .../progress-panel.component.spec.ts | 54 ++++++++++++++++ .../progress-panel.component.ts | 55 ++++++++-------- .../scheduler-control.component.spec.ts | 30 +++++---- .../simple-trigger-config.component.spec.ts | 64 +++++++++++++------ .../simple-trigger-config.component.ts | 33 +++++++--- quartz-manager-frontend/src/app/polyfills.ts | 8 +-- .../logs.rx-websocket.service.spec.ts | 24 ++++++- .../progress.rx-websocket.service.spec.ts | 39 +++++++++++ .../src/app/services/user.service.ts | 2 +- .../app/views/manager/manager.component.html | 9 +-- .../app/views/manager/manager.component.ts | 15 +++-- quartz-manager-frontend/src/polyfills.ts | 8 +-- quartz-manager-frontend/src/typings.d.ts | 2 +- quartz-manager-parent/.gitignore | 1 + quartz-manager-parent/pom.xml | 6 ++ .../SimpleTriggerServiceIntegrationTest.java | 56 ++++++++++++++-- .../api/websockets/WebSocketNotifierTest.java | 48 ++++++++++++++ 23 files changed, 496 insertions(+), 117 deletions(-) create mode 100644 quartz-manager-frontend/.eslintrc.sonar.json create mode 100644 quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts create mode 100644 quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts create mode 100644 quartz-manager-frontend/src/app/services/progress.rx-websocket.service.spec.ts create mode 100644 quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketNotifierTest.java diff --git a/quartz-manager-frontend/.eslintrc.sonar.json b/quartz-manager-frontend/.eslintrc.sonar.json new file mode 100644 index 0000000..d98850c --- /dev/null +++ b/quartz-manager-frontend/.eslintrc.sonar.json @@ -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 +} diff --git a/quartz-manager-frontend/package-lock.json b/quartz-manager-frontend/package-lock.json index f5bb1db..90398ad 100644 --- a/quartz-manager-frontend/package-lock.json +++ b/quartz-manager-frontend/package-lock.json @@ -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, diff --git a/quartz-manager-frontend/package.json b/quartz-manager-frontend/package.json index b390f5b..77c9d3b 100644 --- a/quartz-manager-frontend/package.json +++ b/quartz-manager-frontend/package.json @@ -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", diff --git a/quartz-manager-frontend/src/_test.ts b/quartz-manager-frontend/src/_test.ts index 9bf7226..1cfaeb5 100644 --- a/quartz-manager-frontend/src/_test.ts +++ b/quartz-manager-frontend/src/_test.ts @@ -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 () {}; diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts new file mode 100644 index 0000000..d157604 --- /dev/null +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts @@ -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(); + 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(); + const secondMessages = new Subject(); + 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(); + }); + +}); diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts index ad390d2..4e2f292 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts @@ -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) { diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts new file mode 100644 index 0000000..d842cca --- /dev/null +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts @@ -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(); + 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(); + const secondMessages = new Subject(); + 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(); + }); + +}); diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts index 8cdc5ba..6713884 100644 --- a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts @@ -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; + } + } + +} diff --git a/quartz-manager-frontend/src/app/components/scheduler-control/scheduler-control.component.spec.ts b/quartz-manager-frontend/src/app/components/scheduler-control/scheduler-control.component.spec.ts index 266fe11..2245bb0 100644 --- a/quartz-manager-frontend/src/app/components/scheduler-control/scheduler-control.component.spec.ts +++ b/quartz-manager-frontend/src/app/components/scheduler-control/scheduler-control.component.spec.ts @@ -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; @@ -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(); diff --git a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts index d186a80..422752f 100644 --- a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts +++ b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.spec.ts @@ -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; @@ -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 = {jobClassName: 'TestJob', description: null}; + mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null); + mockTrigger.jobDetailDTO = {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 = {jobClassName: 'TestJob', description: null}; + mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null); + mockTrigger.jobDetailDTO = {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 = {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(); }); diff --git a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts index 000d7a4..99ae80f 100644 --- a/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts +++ b/quartz-manager-frontend/src/app/components/simple-trigger-config/simple-trigger-config.component.ts @@ -47,6 +47,9 @@ export class SimpleTriggerConfigComponent implements OnInit { @Output() onNewTrigger = new EventEmitter(); + @Output() + triggerFormOpenChange = new EventEmitter(); + 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 +}); } diff --git a/quartz-manager-frontend/src/app/polyfills.ts b/quartz-manager-frontend/src/app/polyfills.ts index 622efa0..766305d 100644 --- a/quartz-manager-frontend/src/app/polyfills.ts +++ b/quartz-manager-frontend/src/app/polyfills.ts @@ -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'; diff --git a/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts index c208cce..64fa05e 100644 --- a/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts +++ b/quartz-manager-frontend/src/app/services/logs.rx-websocket.service.spec.ts @@ -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='); + }); }); diff --git a/quartz-manager-frontend/src/app/services/progress.rx-websocket.service.spec.ts b/quartz-manager-frontend/src/app/services/progress.rx-websocket.service.spec.ts new file mode 100644 index 0000000..cf32c29 --- /dev/null +++ b/quartz-manager-frontend/src/app/services/progress.rx-websocket.service.spec.ts @@ -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='); + }); +}); diff --git a/quartz-manager-frontend/src/app/services/user.service.ts b/quartz-manager-frontend/src/app/services/user.service.ts index ae0bf61..224953f 100644 --- a/quartz-manager-frontend/src/app/services/user.service.ts +++ b/quartz-manager-frontend/src/app/services/user.service.ts @@ -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; diff --git a/quartz-manager-frontend/src/app/views/manager/manager.component.html b/quartz-manager-frontend/src/app/views/manager/manager.component.html index cc71362..1428332 100644 --- a/quartz-manager-frontend/src/app/views/manager/manager.component.html +++ b/quartz-manager-frontend/src/app/views/manager/manager.component.html @@ -19,10 +19,11 @@
- - + +
diff --git a/quartz-manager-frontend/src/app/views/manager/manager.component.ts b/quartz-manager-frontend/src/app/views/manager/manager.component.ts index 8c010dd..20a9a7d 100644 --- a/quartz-manager-frontend/src/app/views/manager/manager.component.ts +++ b/quartz-manager-frontend/src/app/views/manager/manager.component.ts @@ -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; } } diff --git a/quartz-manager-frontend/src/polyfills.ts b/quartz-manager-frontend/src/polyfills.ts index ecc7cec..95ab387 100644 --- a/quartz-manager-frontend/src/polyfills.ts +++ b/quartz-manager-frontend/src/polyfills.ts @@ -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'; diff --git a/quartz-manager-frontend/src/typings.d.ts b/quartz-manager-frontend/src/typings.d.ts index ef5c7bd..388fb92 100644 --- a/quartz-manager-frontend/src/typings.d.ts +++ b/quartz-manager-frontend/src/typings.d.ts @@ -1,5 +1,5 @@ /* SystemJS module definition */ -declare var module: NodeModule; +declare let module: NodeModule; interface NodeModule { id: string; } diff --git a/quartz-manager-parent/.gitignore b/quartz-manager-parent/.gitignore index a080dd8..a3232ff 100644 --- a/quartz-manager-parent/.gitignore +++ b/quartz-manager-parent/.gitignore @@ -4,3 +4,4 @@ .classpath .project .idea +/**/*.iml diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index a9e3bea..8794fae 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -50,6 +50,7 @@ 1.6.7 2.5.3 3.0.1 + 3.11.0.3922 fabioformosa https://sonarcloud.io @@ -133,6 +134,11 @@ maven-failsafe-plugin ${maven-failsafe-plugin.version}
+ + org.sonarsource.scanner.maven + sonar-maven-plugin + ${sonar-maven-plugin.version} + org.jacoco jacoco-maven-plugin diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/services/SimpleTriggerServiceIntegrationTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/services/SimpleTriggerServiceIntegrationTest.java index f26e829..74c633d 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/services/SimpleTriggerServiceIntegrationTest.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/services/SimpleTriggerServiceIntegrationTest.java @@ -18,13 +18,17 @@ import java.util.Date; @SpringBootTest 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 private SimpleTriggerService simpleTriggerService; @Test void givenASimpleTriggerCommandDTOWithAllData_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException { String simpleTriggerTestName = "simpleTriggerWithAllData"; - String jobClass = "it.fabioformosa.quartzmanager.api.jobs.SampleJob"; Date startDate = new Date(); Date endDate = DateUtils.addHoursToNow(5); int repeatCount = 3; @@ -41,7 +45,7 @@ class SimpleTriggerServiceIntegrationTest { .repeatCount(repeatCount) .repeatInterval(repeatInterval) .misfireInstruction(misfireInstructionFireNow) - .jobClass(jobClass) + .jobClass(SAMPLE_JOB_CLASS) .build()) .build(); SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand); @@ -61,12 +65,11 @@ class SimpleTriggerServiceIntegrationTest { @Test void givenASimpleTriggerCommandDTOWithMissingOptionalField_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException { String simpleTriggerTestName = "simpleTriggerWithoutOptionalData"; - String jobClass = "it.fabioformosa.quartzmanager.api.jobs.SampleJob"; SimpleTriggerCommandDTO simpleTriggerCommand = SimpleTriggerCommandDTO.builder() .triggerName(simpleTriggerTestName) .simpleTriggerInputDTO(SimpleTriggerInputDTO.builder() - .jobClass(jobClass) + .jobClass(SAMPLE_JOB_CLASS) .build()) .build(); SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand); @@ -81,4 +84,49 @@ class SimpleTriggerServiceIntegrationTest { 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(); + } + } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketNotifierTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketNotifierTest.java new file mode 100644 index 0000000..94b38ad --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/websockets/WebSocketNotifierTest.java @@ -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); + } + +} From d7a78c57aeb2ca6a92f7b7dc7f56a86e0afa6361 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 6 May 2026 23:13:44 +0200 Subject: [PATCH 38/40] #103 minor enhancements --- .../logs-panel/logs-panel.component.html | 16 +++--- .../logs-panel/logs-panel.component.scss | 17 +++++-- .../logs-panel/logs-panel.component.spec.ts | 49 +++++++++++++++++++ .../logs-panel/logs-panel.component.ts | 18 ++++++- .../progress-panel.component.html | 4 +- .../progress-panel.component.scss | 18 +++++++ .../progress-panel.component.spec.ts | 48 ++++++++++++++++++ .../progress-panel.component.ts | 41 +++++++++++++--- 8 files changed, 189 insertions(+), 22 deletions(-) diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.html b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.html index eccd084..7d8d78c 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.html +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.html @@ -3,12 +3,16 @@ JOB LOGS -
-
- no logs -
-
-
+
+
+ no logs +
+
+
+ +
Waiting for logs from {{selectedTriggerName}}...
+
+
diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.scss b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.scss index 9529fd0..104f119 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.scss +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.scss @@ -9,11 +9,18 @@ color: gold; } -#logs{ - font-size: 1em; -} - -/* ===== Scrollbar CSS ===== */ +#logs{ + font-size: 1em; +} + +.waitingLogs { + color: #6b7280; + height: 100%; + min-height: 180px; + text-align: center; +} + +/* ===== Scrollbar CSS ===== */ /* Firefox */ * { scrollbar-width: auto; diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts index d157604..414e6c3 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.spec.ts @@ -15,6 +15,8 @@ describe('LogsPanelComponent', () => { component.triggerKey = new TriggerKey('trigger-1', null); expect(logsRxWebsocketService.watch).toHaveBeenCalledWith('/topic/logs/trigger-1'); + expect(component.selectedTriggerName).toEqual('trigger-1'); + expect(component.isWaitingForLogs()).toBeTruthy(); const logRecord = { date: new Date(), @@ -30,6 +32,7 @@ describe('LogsPanelComponent', () => { msg: 'job completed', threadName: 'worker-1' }); + expect(component.isWaitingForLogs()).toBeFalsy(); }); it('should unsubscribe from the previous topic when the trigger changes', () => { @@ -52,6 +55,52 @@ describe('LogsPanelComponent', () => { expect(logsRxWebsocketService.watch).toHaveBeenCalledWith('/topic/logs/trigger-2'); }); + it('should clear logs when the trigger changes', () => { + const firstMessages = new Subject(); + const secondMessages = new Subject(); + const logsRxWebsocketService = { + watch: jest.fn() + .mockReturnValueOnce(firstMessages.asObservable()) + .mockReturnValueOnce(secondMessages.asObservable()) + .mockReturnValueOnce(firstMessages.asObservable()) + }; + const component = new LogsPanelComponent(logsRxWebsocketService as any, null); + + component.triggerKey = new TriggerKey('trigger-1', null); + firstMessages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})}); + expect(component.logs.length).toEqual(1); + + component.triggerKey = new TriggerKey('trigger-2', null); + expect(component.logs).toEqual([]); + expect(component.selectedTriggerName).toEqual('trigger-2'); + expect(component.isWaitingForLogs()).toBeTruthy(); + + secondMessages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'second log', threadName: 'worker-2'})}); + expect(component.logs.length).toEqual(1); + + component.triggerKey = new TriggerKey('trigger-1', null); + expect(component.logs).toEqual([]); + expect(component.selectedTriggerName).toEqual('trigger-1'); + expect(component.isWaitingForLogs()).toBeTruthy(); + }); + + it('should clear logs when no trigger is selected', () => { + const messages = new Subject(); + const logsRxWebsocketService = { + watch: jest.fn(() => messages.asObservable()) + }; + const component = new LogsPanelComponent(logsRxWebsocketService as any, null); + + component.triggerKey = new TriggerKey('trigger-1', null); + messages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})}); + + component.triggerKey = null; + + expect(component.logs).toEqual([]); + expect(component.selectedTriggerName).toBeNull(); + expect(component.isWaitingForLogs()).toBeFalsy(); + }); + it('should ignore destroy when no topic was selected', () => { const logsRxWebsocketService = { watch: jest.fn() diff --git a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts index 4e2f292..6b4403d 100644 --- a/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/logs-panel/logs-panel.component.ts @@ -15,7 +15,9 @@ export class LogsPanelComponent implements OnInit, OnDestroy { MAX_LOGS = 30; - logs = new Array(); + logs = new Array(); + + selectedTriggerName: string; topicSubscription; @@ -32,12 +34,22 @@ export class LogsPanelComponent implements OnInit, OnDestroy { if (!triggerKey || !triggerKey.name) { this._unsubscribeFromTopic(); this.selectedTriggerKey = null; + this.selectedTriggerName = null; + this._resetLogs(); return; } + if (this.selectedTriggerKey?.name === triggerKey.name) { + return; + } + + this._resetLogs(); this.selectedTriggerKey = {...triggerKey} as TriggerKey; + this.selectedTriggerName = triggerKey.name; this._subscribeToTheTopic(this.selectedTriggerKey); } + + isWaitingForLogs = (): boolean => !!this.selectedTriggerName && (!this.logs || this.logs.length === 0); ngOnInit() { } @@ -63,6 +75,10 @@ export class LogsPanelComponent implements OnInit, OnDestroy { this.topicSubscription = null; } } + + private _resetLogs() { + this.logs = []; + } _showNewLog = (logRecord) => { if (this.logs.length > this.MAX_LOGS) { diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.html b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.html index 6816bcf..9e2c21e 100644 --- a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.html +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.html @@ -6,12 +6,12 @@
--> - + JOB PROGRESS -
+
{{percentageStr}}
diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.scss b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.scss index 7aa05ec..51c57ca 100644 --- a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.scss +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.scss @@ -31,3 +31,21 @@ .fireBoxContent{ text-align: center; } + +.progress-updated { + animation: progressUpdatePulse 700ms ease-out; +} + +@keyframes progressUpdatePulse { + 0% { + box-shadow: 0 0 0 0 rgba(63, 81, 181, 0.35); + } + + 45% { + box-shadow: 0 0 0 6px rgba(63, 81, 181, 0.16); + } + + 100% { + box-shadow: 0 0 0 0 rgba(63, 81, 181, 0); + } +} diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts index d842cca..05b3f86 100644 --- a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.spec.ts @@ -6,6 +6,7 @@ import {jest} from '@jest/globals'; describe('ProgressPanelComponent', () => { it('should subscribe to the selected trigger progress topic', () => { + jest.useFakeTimers(); const messages = new Subject(); const progressRxWebsocketService = { watch: jest.fn(() => messages.asObservable()) @@ -17,9 +18,12 @@ describe('ProgressPanelComponent', () => { expect(progressRxWebsocketService.watch).toHaveBeenCalledWith('/topic/progress/trigger-1'); messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})}); + jest.runOnlyPendingTimers(); expect(component.progress.percentage).toEqual(75); expect(component.percentageStr).toEqual('75%'); + expect(component.progressUpdated).toBeTruthy(); + jest.useRealTimers(); }); it('should unsubscribe from the previous topic when the trigger changes', () => { @@ -42,6 +46,50 @@ describe('ProgressPanelComponent', () => { expect(progressRxWebsocketService.watch).toHaveBeenCalledWith('/topic/progress/trigger-2'); }); + it('should reset progress when the trigger changes', () => { + const firstMessages = new Subject(); + const secondMessages = new Subject(); + const progressRxWebsocketService = { + watch: jest.fn() + .mockReturnValueOnce(firstMessages.asObservable()) + .mockReturnValueOnce(secondMessages.asObservable()) + .mockReturnValueOnce(firstMessages.asObservable()) + }; + const component = new ProgressPanelComponent(progressRxWebsocketService as any); + + component.triggerKey = new TriggerKey('trigger-1', null); + firstMessages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})}); + expect(component.progress.percentage).toEqual(75); + + component.triggerKey = new TriggerKey('trigger-2', null); + expect(component.progress.percentage).toEqual(-1); + expect(component.percentageStr).toBeNull(); + expect(component.progressUpdated).toBeFalsy(); + + secondMessages.next({body: JSON.stringify({percentage: 20, timesTriggered: 1})}); + expect(component.progress.percentage).toEqual(20); + + component.triggerKey = new TriggerKey('trigger-1', null); + expect(component.progress.percentage).toEqual(-1); + }); + + it('should reset progress when no trigger is selected', () => { + const messages = new Subject(); + const progressRxWebsocketService = { + watch: jest.fn(() => messages.asObservable()) + }; + const component = new ProgressPanelComponent(progressRxWebsocketService as any); + + component.triggerKey = new TriggerKey('trigger-1', null); + messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})}); + + component.triggerKey = null; + + expect(component.progress.percentage).toEqual(-1); + expect(component.percentageStr).toBeNull(); + expect(component.progressUpdated).toBeFalsy(); + }); + it('should ignore destroy when no topic was selected', () => { const progressRxWebsocketService = { watch: jest.fn() diff --git a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts index 6713884..550305a 100644 --- a/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts +++ b/quartz-manager-frontend/src/app/components/progress-panel/progress-panel.component.ts @@ -9,10 +9,11 @@ import {map} from 'rxjs/operators'; templateUrl: './progress-panel.component.html', styleUrls: ['./progress-panel.component.scss'] }) -export class ProgressPanelComponent implements OnInit, OnDestroy { - - progress: TriggerFiredBundle = new TriggerFiredBundle(); - percentageStr: string; +export class ProgressPanelComponent implements OnInit, OnDestroy { + + progress: TriggerFiredBundle = ProgressPanelComponent._buildEmptyProgress(); + percentageStr: string; + progressUpdated = false; topicSubscription; private selectedTriggerKey: TriggerKey; @@ -26,9 +27,15 @@ export class ProgressPanelComponent implements OnInit, OnDestroy { if (!triggerKey || !triggerKey.name) { this._unsubscribeFromTopic(); this.selectedTriggerKey = null; + this._resetProgress(); return; } + if (this.selectedTriggerKey?.name === triggerKey.name) { + return; + } + + this._resetProgress(); this.selectedTriggerKey = {...triggerKey} as TriggerKey; this._subscribeToTheTopic(this.selectedTriggerKey); } @@ -44,10 +51,11 @@ export class ProgressPanelComponent implements OnInit, OnDestroy { }); }; - onNewProgressMsg = (receivedMsg) => { - this.progress = receivedMsg; - this.percentageStr = this.progress.percentage + '%'; - } + onNewProgressMsg = (receivedMsg) => { + this.progress = receivedMsg; + this.percentageStr = this.progress.percentage + '%'; + this._markProgressUpdated(); + } ngOnInit() { } @@ -63,4 +71,21 @@ export class ProgressPanelComponent implements OnInit, OnDestroy { } } + private _resetProgress() { + this.progress = ProgressPanelComponent._buildEmptyProgress(); + this.percentageStr = null; + this.progressUpdated = false; + } + + private _markProgressUpdated() { + this.progressUpdated = false; + setTimeout(() => this.progressUpdated = true); + } + + private static _buildEmptyProgress() { + const progress = new TriggerFiredBundle(); + progress.percentage = -1; + return progress; + } + } From b2f9692815626f50be0a75f2101a0491850d8d65 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 6 May 2026 23:41:08 +0200 Subject: [PATCH 39/40] #103 fixed a test --- .../fabioformosa/quartzmanager/api/jobs/SampleJobTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java index f233979..e7c2305 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java @@ -13,6 +13,7 @@ import org.mockito.MockitoAnnotations; import org.quartz.*; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; class SampleJobTest { @@ -49,14 +50,14 @@ class SampleJobTest { Mockito.when(jobExecutionContext.getJobDetail()).thenReturn(jobDetail); sampleJob.execute(jobExecutionContext); - Mockito.verify(webSocketLogsNotifier).send(triggerName, argThat(actualLogRecord -> { + Mockito.verify(webSocketLogsNotifier).send(eq(triggerName), argThat(actualLogRecord -> { Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!"); Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO); Assertions.assertThat(actualLogRecord.getDate()).isNotNull(); Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull(); return true; })); - Mockito.verify(webSocketProgressNotifier).send(triggerName, argThat(triggerFiredBundleDTO -> { + Mockito.verify(webSocketProgressNotifier).send(eq(triggerName), argThat(triggerFiredBundleDTO -> { Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job"); Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6); Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName()); From 068b0eed34bdb1962c7e963a0c11984dfc9a5a13 Mon Sep 17 00:00:00 2001 From: Fabio Formosa Date: Wed, 6 May 2026 23:43:35 +0200 Subject: [PATCH 40/40] #103 fixed a test --- .../quartzmanager/api/jobs/SampleJobTest.java | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java index e7c2305..2ffde76 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/api/jobs/SampleJobTest.java @@ -4,17 +4,26 @@ import it.fabioformosa.quartzmanager.api.dto.TriggerFiredBundleDTO; import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord; import it.fabioformosa.quartzmanager.api.websockets.WebhookSender; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.quartz.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobKey; +import org.quartz.ScheduleBuilder; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +@ExtendWith(MockitoExtension.class) class SampleJobTest { @InjectMocks @@ -25,10 +34,11 @@ class SampleJobTest { @Mock private WebhookSender webSocketLogsNotifier; - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } + @Captor + private ArgumentCaptor logRecordCaptor; + + @Captor + private ArgumentCaptor triggerFiredBundleDTOCaptor; @Test void givenASampleJob_whenTheJobIsExecuted_thenTheWebhookSendersAreCalled() { @@ -50,24 +60,23 @@ class SampleJobTest { Mockito.when(jobExecutionContext.getJobDetail()).thenReturn(jobDetail); sampleJob.execute(jobExecutionContext); - Mockito.verify(webSocketLogsNotifier).send(eq(triggerName), argThat(actualLogRecord -> { - Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!"); - Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO); - Assertions.assertThat(actualLogRecord.getDate()).isNotNull(); - Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull(); - return true; - })); - Mockito.verify(webSocketProgressNotifier).send(eq(triggerName), argThat(triggerFiredBundleDTO -> { - Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job"); - Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6); - Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName()); - Assertions.assertThat(triggerFiredBundleDTO.getTimesTriggered()).isZero(); - Assertions.assertThat(triggerFiredBundleDTO.getNextFireTime()).isNull(); - Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isZero(); - Assertions.assertThat(triggerFiredBundleDTO.getFinalFireTime()).isNotNull(); - Assertions.assertThat(triggerFiredBundleDTO.getPreviousFireTime()).isNull(); - return true; - })); + Mockito.verify(webSocketLogsNotifier).send(eq(triggerName), logRecordCaptor.capture()); + LogRecord actualLogRecord = logRecordCaptor.getValue(); + Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!"); + Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO); + Assertions.assertThat(actualLogRecord.getDate()).isNotNull(); + Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull(); + + Mockito.verify(webSocketProgressNotifier).send(eq(triggerName), triggerFiredBundleDTOCaptor.capture()); + TriggerFiredBundleDTO triggerFiredBundleDTO = triggerFiredBundleDTOCaptor.getValue(); + Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job"); + Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6); + Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName()); + Assertions.assertThat(triggerFiredBundleDTO.getTimesTriggered()).isZero(); + Assertions.assertThat(triggerFiredBundleDTO.getNextFireTime()).isNull(); + Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isZero(); + Assertions.assertThat(triggerFiredBundleDTO.getFinalFireTime()).isNotNull(); + Assertions.assertThat(triggerFiredBundleDTO.getPreviousFireTime()).isNull(); } @Test