mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-15 06:10:29 +09:00
This commit is contained in:
30
quartz-manager-frontend/eslint.sonar.config.mjs
Normal file
30
quartz-manager-frontend/eslint.sonar.config.mjs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import sonarjs from 'eslint-plugin-sonarjs';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
files: ['src/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
sourceType: 'module'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
sonarjs
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...sonarjs.configs.recommended.rules,
|
||||||
|
'sonarjs/deprecation': 'off',
|
||||||
|
'sonarjs/no-commented-code': 'off',
|
||||||
|
'sonarjs/no-dead-store': 'off',
|
||||||
|
'sonarjs/no-incomplete-assertions': 'off',
|
||||||
|
'sonarjs/no-primitive-wrappers': 'off',
|
||||||
|
'sonarjs/no-unused-vars': 'off',
|
||||||
|
'sonarjs/prefer-promise-shorthand': 'off',
|
||||||
|
'sonarjs/todo-tag': 'off',
|
||||||
|
'sonarjs/unused-import': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"build": "ng build --configuration production",
|
"build": "ng build --configuration production",
|
||||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"lint:sonar": "eslint --no-eslintrc -c .eslintrc.sonar.json \"src/**/*.ts\"",
|
"lint:sonar": "eslint -c eslint.sonar.config.mjs \"src/**/*.ts\"",
|
||||||
"lint:sonar:fix": "eslint --no-eslintrc -c .eslintrc.sonar.json \"src/**/*.ts\" --fix"
|
"lint:sonar:fix": "eslint -c eslint.sonar.config.mjs \"src/**/*.ts\" --fix"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class SchedulerControlComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
|
|
||||||
stopScheduler = function () {
|
stopScheduler = function () {
|
||||||
this.schedulerService.stopScheduler().subscribe((res) => {
|
this.schedulerService.shutdownScheduler().subscribe((res) => {
|
||||||
this.scheduler.status = 'STOPPED'
|
this.scheduler.status = 'STOPPED'
|
||||||
}, (res) => {
|
}, (res) => {
|
||||||
console.log(JSON.stringify(res))
|
console.log(JSON.stringify(res))
|
||||||
@@ -44,7 +44,7 @@ export class SchedulerControlComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pauseScheduler = function () {
|
pauseScheduler = function () {
|
||||||
this.schedulerService.pauseScheduler().subscribe((res) => {
|
this.schedulerService.standbyScheduler().subscribe((res) => {
|
||||||
this.scheduler.status = 'PAUSED'
|
this.scheduler.status = 'PAUSED'
|
||||||
}, (res) => {
|
}, (res) => {
|
||||||
console.log(JSON.stringify(res))
|
console.log(JSON.stringify(res))
|
||||||
|
|||||||
12
quartz-manager-frontend/src/app/model/scheduled-job.model.ts
Normal file
12
quartz-manager-frontend/src/app/model/scheduled-job.model.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {JobKeyModel} from './jobKey.model';
|
||||||
|
import {TriggerKey} from './triggerKey.model';
|
||||||
|
|
||||||
|
export class ScheduledJob {
|
||||||
|
jobKeyDTO: JobKeyModel;
|
||||||
|
jobClassName: string;
|
||||||
|
description: string;
|
||||||
|
durable: boolean;
|
||||||
|
requestsRecovery: boolean;
|
||||||
|
jobDataMap: {[key: string]: unknown};
|
||||||
|
triggerKeys: TriggerKey[];
|
||||||
|
}
|
||||||
@@ -5,6 +5,14 @@ export class Scheduler {
|
|||||||
instanceId: string;
|
instanceId: string;
|
||||||
status: string;
|
status: string;
|
||||||
triggerKeys: TriggerKey[];
|
triggerKeys: TriggerKey[];
|
||||||
|
quartzVersion: string;
|
||||||
|
jobStoreClass: string;
|
||||||
|
jobStoreSupportsPersistence: boolean;
|
||||||
|
clustered: boolean;
|
||||||
|
threadPoolClass: string;
|
||||||
|
threadPoolSize: number;
|
||||||
|
runningSince: string;
|
||||||
|
numberOfJobsExecuted: number;
|
||||||
|
|
||||||
constructor(name: string, instanceId: string, status: string, triggerKeys: TriggerKey[]) {
|
constructor(name: string, instanceId: string, status: string, triggerKeys: TriggerKey[]) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export class SimpleTriggerCommand {
|
export class SimpleTriggerCommand {
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
|
triggerGroup: string;
|
||||||
jobClass: string;
|
jobClass: string;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ export class Trigger {
|
|||||||
finalFireTime: Date;
|
finalFireTime: Date;
|
||||||
misfireInstruction: number;
|
misfireInstruction: number;
|
||||||
nextFireTime: Date;
|
nextFireTime: Date;
|
||||||
|
previousFireTime: Date;
|
||||||
|
type: string;
|
||||||
|
state: string;
|
||||||
|
calendarName: string;
|
||||||
jobKeyDTO: JobKeyModel;
|
jobKeyDTO: JobKeyModel;
|
||||||
jobDetailDTO: JobDetail = new JobDetail();
|
jobDetailDTO: JobDetail = new JobDetail();
|
||||||
mayFireAgain: boolean;
|
mayFireAgain: boolean;
|
||||||
|
|||||||
32
quartz-manager-frontend/src/app/services/job.service.spec.ts
Normal file
32
quartz-manager-frontend/src/app/services/job.service.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import JobService from './job.service';
|
||||||
|
import {ScheduledJob} from '../model/scheduled-job.model';
|
||||||
|
import {jest} from '@jest/globals';
|
||||||
|
|
||||||
|
describe('JobService', () => {
|
||||||
|
let apiService: any;
|
||||||
|
let jobService: JobService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
apiService = {
|
||||||
|
get: jest.fn(),
|
||||||
|
post: jest.fn(),
|
||||||
|
delete: jest.fn()
|
||||||
|
};
|
||||||
|
jobService = new JobService(apiService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses job class and scheduled job endpoints', () => {
|
||||||
|
const job = new ScheduledJob();
|
||||||
|
job.jobKeyDTO = {group: 'DEFAULT', name: 'sampleJob'};
|
||||||
|
|
||||||
|
jobService.fetchJobs();
|
||||||
|
jobService.fetchScheduledJobs();
|
||||||
|
jobService.triggerJob(job);
|
||||||
|
jobService.deleteJob(job);
|
||||||
|
|
||||||
|
expect(apiService.get).toHaveBeenCalledWith('/quartz-manager/job-classes');
|
||||||
|
expect(apiService.get).toHaveBeenCalledWith('/quartz-manager/jobs');
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/jobs/DEFAULT/sampleJob/trigger', {});
|
||||||
|
expect(apiService.delete).toHaveBeenCalledWith('/quartz-manager/jobs/DEFAULT/sampleJob');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
|
|||||||
import {ApiService} from './api.service';
|
import {ApiService} from './api.service';
|
||||||
import {CONTEXT_PATH, getBaseUrl} from './config.service';
|
import {CONTEXT_PATH, getBaseUrl} from './config.service';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
|
import {ScheduledJob} from '../model/scheduled-job.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class JobService {
|
export default class JobService {
|
||||||
@@ -12,7 +13,19 @@ export default class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchJobs = (): Observable<string[]> => {
|
fetchJobs = (): Observable<string[]> => {
|
||||||
|
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/job-classes`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchScheduledJobs = (): Observable<ScheduledJob[]> => {
|
||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/jobs`)
|
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/jobs`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
triggerJob = (job: ScheduledJob): Observable<void> => {
|
||||||
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/jobs/${job.jobKeyDTO.group}/${job.jobKeyDTO.name}/trigger`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteJob = (job: ScheduledJob): Observable<void> => {
|
||||||
|
return this.apiService.delete(getBaseUrl() + `${CONTEXT_PATH}/jobs/${job.jobKeyDTO.group}/${job.jobKeyDTO.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import {SchedulerService} from './scheduler.service';
|
||||||
|
import {SimpleTriggerCommand} from '../model/simple-trigger.command';
|
||||||
|
import {jest} from '@jest/globals';
|
||||||
|
|
||||||
|
describe('SchedulerService', () => {
|
||||||
|
let apiService: any;
|
||||||
|
let schedulerService: SchedulerService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
apiService = {
|
||||||
|
get: jest.fn(),
|
||||||
|
post: jest.fn(),
|
||||||
|
put: jest.fn()
|
||||||
|
};
|
||||||
|
schedulerService = new SchedulerService(apiService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses POST scheduler lifecycle endpoints', () => {
|
||||||
|
schedulerService.startScheduler();
|
||||||
|
schedulerService.standbyScheduler();
|
||||||
|
schedulerService.resumeScheduler();
|
||||||
|
schedulerService.shutdownScheduler();
|
||||||
|
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/scheduler/start', {});
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/scheduler/standby', {});
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/scheduler/resume', {});
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/scheduler/shutdown', {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses grouped simple trigger endpoints', () => {
|
||||||
|
const command = new SimpleTriggerCommand();
|
||||||
|
command.triggerGroup = 'DEFAULT';
|
||||||
|
command.triggerName = 'sampleTrigger';
|
||||||
|
|
||||||
|
schedulerService.getSimpleTriggerConfig(command.triggerName, command.triggerGroup);
|
||||||
|
schedulerService.saveSimpleTriggerConfig(command);
|
||||||
|
schedulerService.updateSimpleTriggerConfig(command);
|
||||||
|
|
||||||
|
expect(apiService.get).toHaveBeenCalledWith('/quartz-manager/simple-triggers/DEFAULT/sampleTrigger');
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/simple-triggers/DEFAULT/sampleTrigger', command);
|
||||||
|
expect(apiService.put).toHaveBeenCalledWith('/quartz-manager/simple-triggers/DEFAULT/sampleTrigger', command);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,19 +15,19 @@ export class SchedulerService {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
startScheduler = (): Observable<void> => {
|
startScheduler = (): Observable<void> => {
|
||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/scheduler/run`);
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/scheduler/start`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
stopScheduler = (): Observable<void> => {
|
shutdownScheduler = (): Observable<void> => {
|
||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/scheduler/stop`);
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/scheduler/shutdown`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseScheduler = (): Observable<void> => {
|
standbyScheduler = (): Observable<void> => {
|
||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/scheduler/pause`);
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/scheduler/standby`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeScheduler = (): Observable<void> => {
|
resumeScheduler = (): Observable<void> => {
|
||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/scheduler/resume`);
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/scheduler/resume`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatus = () => {
|
getStatus = () => {
|
||||||
@@ -38,16 +38,16 @@ export class SchedulerService {
|
|||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/scheduler`);
|
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/scheduler`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSimpleTriggerConfig = (triggerName: string): Observable<Trigger> => {
|
getSimpleTriggerConfig = (triggerName: string, triggerGroup = 'DEFAULT'): Observable<Trigger> => {
|
||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/simple-triggers/${triggerName}`);
|
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/simple-triggers/${triggerGroup}/${triggerName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSimpleTriggerConfig = (config: SimpleTriggerCommand) => {
|
saveSimpleTriggerConfig = (config: SimpleTriggerCommand) => {
|
||||||
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/simple-triggers/${config.triggerName}`, config)
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/simple-triggers/${config.triggerGroup}/${config.triggerName}`, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSimpleTriggerConfig = (config: SimpleTriggerCommand) => {
|
updateSimpleTriggerConfig = (config: SimpleTriggerCommand) => {
|
||||||
return this.apiService.put(getBaseUrl() + `${CONTEXT_PATH}/simple-triggers/${config.triggerName}`, config)
|
return this.apiService.put(getBaseUrl() + `${CONTEXT_PATH}/simple-triggers/${config.triggerGroup}/${config.triggerName}`, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import {TriggerService} from './trigger.service';
|
||||||
|
import {TriggerKey} from '../model/triggerKey.model';
|
||||||
|
import {jest} from '@jest/globals';
|
||||||
|
|
||||||
|
describe('TriggerService', () => {
|
||||||
|
let apiService: any;
|
||||||
|
let triggerService: TriggerService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
apiService = {
|
||||||
|
get: jest.fn(),
|
||||||
|
post: jest.fn(),
|
||||||
|
delete: jest.fn()
|
||||||
|
};
|
||||||
|
triggerService = new TriggerService(apiService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses grouped trigger lifecycle endpoints', () => {
|
||||||
|
const triggerKey = new TriggerKey('sampleTrigger', 'DEFAULT');
|
||||||
|
|
||||||
|
triggerService.getTrigger(triggerKey);
|
||||||
|
triggerService.pauseTrigger(triggerKey);
|
||||||
|
triggerService.resumeTrigger(triggerKey);
|
||||||
|
triggerService.unscheduleTrigger(triggerKey);
|
||||||
|
|
||||||
|
expect(apiService.get).toHaveBeenCalledWith('/quartz-manager/triggers/DEFAULT/sampleTrigger');
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/triggers/DEFAULT/sampleTrigger/pause', {});
|
||||||
|
expect(apiService.post).toHaveBeenCalledWith('/quartz-manager/triggers/DEFAULT/sampleTrigger/resume', {});
|
||||||
|
expect(apiService.delete).toHaveBeenCalledWith('/quartz-manager/triggers/DEFAULT/sampleTrigger');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,5 +16,20 @@ export class TriggerService {
|
|||||||
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/triggers`);
|
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/triggers`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTrigger = (triggerKey: TriggerKey): Observable<Trigger> => {
|
||||||
|
return this.apiService.get(getBaseUrl() + `${CONTEXT_PATH}/triggers/${triggerKey.group || 'DEFAULT'}/${triggerKey.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseTrigger = (triggerKey: TriggerKey): Observable<void> => {
|
||||||
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/triggers/${triggerKey.group || 'DEFAULT'}/${triggerKey.name}/pause`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeTrigger = (triggerKey: TriggerKey): Observable<void> => {
|
||||||
|
return this.apiService.post(getBaseUrl() + `${CONTEXT_PATH}/triggers/${triggerKey.group || 'DEFAULT'}/${triggerKey.name}/resume`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
unscheduleTrigger = (triggerKey: TriggerKey): Observable<void> => {
|
||||||
|
return this.apiService.delete(getBaseUrl() + `${CONTEXT_PATH}/triggers/${triggerKey.group || 'DEFAULT'}/${triggerKey.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="chip" [ngClass]="getSchedulerStatusClass()">{{ scheduler?.status || 'LOADING' }}</span>
|
<span class="chip" [ngClass]="getSchedulerStatusClass()">{{ scheduler?.status || 'LOADING' }}</span>
|
||||||
<div class="kv"><span>Instance ID</span><span>{{ scheduler?.instanceId || '-' }}</span></div>
|
<div class="kv"><span>Instance ID</span><span>{{ scheduler?.instanceId || '-' }}</span></div>
|
||||||
<button type="button" class="kv kv-button" data-roadmap="Cluster metadata is not exposed by the current backend"><span>Cluster</span><span>Roadmap</span></button>
|
<div class="kv"><span>Cluster</span><span>{{ scheduler?.clustered ? 'YES' : 'NO' }}</span></div>
|
||||||
<div class="kv"><span>WebSocket</span><span>OPEN</span></div>
|
<div class="kv"><span>WebSocket</span><span>OPEN</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions compact-actions" aria-label="Compact scheduler status actions">
|
<div class="actions compact-actions" aria-label="Compact scheduler status actions">
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<div class="field"><label>Status</label><strong>{{ scheduler?.status || '-' }}</strong></div>
|
<div class="field"><label>Status</label><strong>{{ scheduler?.status || '-' }}</strong></div>
|
||||||
<div class="field"><label>Triggers</label><strong>{{ triggerKeys.length }}</strong></div>
|
<div class="field"><label>Triggers</label><strong>{{ triggerKeys.length }}</strong></div>
|
||||||
<div class="field"><label>Eligible jobs</label><strong>{{ jobs.length }}</strong></div>
|
<div class="field"><label>Eligible jobs</label><strong>{{ jobs.length }}</strong></div>
|
||||||
<button type="button" class="field field-button" data-roadmap="Quartz version and job-store metadata are not exposed by the current backend"><label>Quartz metadata</label><strong>Roadmap</strong></button>
|
<div class="field"><label>Quartz metadata</label><strong>{{ scheduler?.quartzVersion || '-' }}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -119,12 +119,12 @@
|
|||||||
<div class="field"><label>Previous fire</label><strong>{{ selectedTrigger?.timesTriggered ? 'tracked by progress events' : 'not exposed' }}</strong></div>
|
<div class="field"><label>Previous fire</label><strong>{{ selectedTrigger?.timesTriggered ? 'tracked by progress events' : 'not exposed' }}</strong></div>
|
||||||
<div class="field"><label>Next fire</label><strong>{{ formatDateTime(selectedTrigger?.nextFireTime) || '-' }}</strong></div>
|
<div class="field"><label>Next fire</label><strong>{{ formatDateTime(selectedTrigger?.nextFireTime) || '-' }}</strong></div>
|
||||||
<div class="field"><label>Priority</label><strong>{{ selectedTrigger?.priority || '-' }}</strong></div>
|
<div class="field"><label>Priority</label><strong>{{ selectedTrigger?.priority || '-' }}</strong></div>
|
||||||
<div class="field"><label>Calendar</label><strong>Roadmap</strong></div>
|
<div class="field"><label>Calendar</label><strong>{{ selectedTrigger?.calendarName || 'none' }}</strong></div>
|
||||||
<div class="field"><label>Misfire</label><strong>{{ selectedTrigger?.misfireInstruction || '-' }}</strong></div>
|
<div class="field"><label>Misfire</label><strong>{{ selectedTrigger?.misfireInstruction || '-' }}</strong></div>
|
||||||
<div class="field"><label>Repeat</label><strong>{{ getSelectedTriggerRepeatSummary() }}</strong></div>
|
<div class="field"><label>Repeat</label><strong>{{ getSelectedTriggerRepeatSummary() }}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-card"><div class="caption">Current run progress</div><div class="progress-line"><span [style.width.%]="getProgressPercentage()"></span></div><div class="mono">{{ getProgressLabel() }}</div></div>
|
<div class="progress-card"><div class="caption">Current run progress</div><div class="progress-line"><span [style.width.%]="getProgressPercentage()"></span></div><div class="mono">{{ getProgressLabel() }}</div></div>
|
||||||
<div class="actions"><button type="button" class="btn" data-roadmap="Trigger pause/resume endpoints are not available yet">Pause</button><button type="button" class="btn" (click)="openRescheduleWizard()">Reschedule</button><button type="button" class="btn danger" data-roadmap="Unschedule trigger is on the roadmap">Unschedule</button></div>
|
<div class="actions"><button type="button" class="btn" (click)="pauseSelectedTrigger()">Pause</button><button type="button" class="btn" (click)="resumeSelectedTrigger()">Resume</button><button type="button" class="btn" (click)="openRescheduleWizard()">Reschedule</button><button type="button" class="btn danger" (click)="unscheduleSelectedTrigger()">Unschedule</button></div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="drawer-title"><div><span class="chip warn">EMPTY</span><h2>No trigger selected</h2><div class="caption">Create a SimpleTrigger or refresh trigger keys.</div></div><button type="button" class="drawer-close" (click)="closeDetailDrawer()">Close</button></div>
|
<div class="drawer-title"><div><span class="chip warn">EMPTY</span><h2>No trigger selected</h2><div class="caption">Create a SimpleTrigger or refresh trigger keys.</div></div><button type="button" class="drawer-close" (click)="closeDetailDrawer()">Close</button></div>
|
||||||
}
|
}
|
||||||
@@ -163,36 +163,38 @@
|
|||||||
|
|
||||||
<div class="page" [class.active]="activePage === 'jobs'">
|
<div class="page" [class.active]="activePage === 'jobs'">
|
||||||
<div class="page-kicker">
|
<div class="page-kicker">
|
||||||
<div><h2>Jobs</h2><p>The current backend exposes eligible Quartz Manager job classes. Full job registry metadata, CRUD, durability, recovery, and group operations remain roadmap features.</p></div>
|
<div><h2>Jobs</h2><p>The backend exposes scheduled Quartz jobs plus eligible job classes for SimpleTrigger creation. Durability, recovery, data map, and related trigger keys are read-only in this release.</p></div>
|
||||||
<div class="toolbar"><input class="search" value="Filter jobs, groups, classes" data-roadmap="Job filtering is on the roadmap"><button type="button" class="btn primary" data-roadmap="Creating jobs from the UI is on the roadmap">New Job</button></div>
|
<div class="toolbar"><input class="search" value="Filter jobs, groups, classes" data-roadmap="Job filtering is on the roadmap"><button type="button" class="btn primary" data-roadmap="Creating jobs from the UI is on the roadmap">New Job</button></div>
|
||||||
</div>
|
</div>
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<div class="card-header"><h2 class="card-title">Eligible Job Classes</h2><div class="toolbar"><span class="chip normal">{{ jobs.length }} JOBS</span><button type="button" class="btn" data-roadmap="Pause job group is on the roadmap">Pause Group</button><button type="button" class="btn" data-roadmap="Job export is on the roadmap">Export</button></div></div>
|
<div class="card-header"><h2 class="card-title">Scheduled Jobs</h2><div class="toolbar"><span class="chip normal">{{ scheduledJobs.length }} JOBS</span><button type="button" class="btn" data-roadmap="Pause job group is on the roadmap">Pause Group</button><button type="button" class="btn" data-roadmap="Job export is on the roadmap">Export</button></div></div>
|
||||||
<div class="split">
|
<div class="split">
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th style="width:22%">Job key</th><th style="width:48%">Class</th><th style="width:10%">Durable</th><th style="width:10%">Recovery</th><th style="width:10%">Triggers</th></tr></thead>
|
<thead><tr><th style="width:22%">Job key</th><th style="width:48%">Class</th><th style="width:10%">Durable</th><th style="width:10%">Recovery</th><th style="width:10%">Triggers</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (jobClass of getJobClassRows(); track jobClass) {
|
@for (job of getScheduledJobRows(); track job.jobKeyDTO.group + '.' + job.jobKeyDTO.name) {
|
||||||
<tr class="selectable" [class.selected]="selectedJobClass === jobClass" (click)="selectJob(jobClass)"><td class="mono">{{ shortClassName(jobClass) }}</td><td class="mono">{{ jobClass }}</td><td><span class="chip warn">Roadmap</span></td><td><span class="chip warn">Roadmap</span></td><td class="mono">Roadmap</td></tr>
|
<tr class="selectable" [class.selected]="selectedScheduledJob?.jobKeyDTO?.name === job.jobKeyDTO.name && selectedScheduledJob?.jobKeyDTO?.group === job.jobKeyDTO.group" (click)="selectScheduledJob(job)"><td class="mono">{{ job.jobKeyDTO.group }}.{{ job.jobKeyDTO.name }}</td><td class="mono">{{ job.jobClassName }}</td><td><span class="chip" [ngClass]="job.durable ? 'normal' : 'warn'">{{ job.durable ? 'YES' : 'NO' }}</span></td><td><span class="chip" [ngClass]="job.requestsRecovery ? 'normal' : 'warn'">{{ job.requestsRecovery ? 'YES' : 'NO' }}</span></td><td class="mono">{{ job.triggerKeys?.length || 0 }}</td></tr>
|
||||||
|
} @empty {
|
||||||
|
<tr><td colspan="5">No scheduled jobs returned by the backend. Create a SimpleTrigger from an eligible job class.</td></tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<aside class="detail drawer detail-drawer" [class.drawer-open]="detailDrawerOpen && activePage === 'jobs'" aria-label="Job detail drawer">
|
<aside class="detail drawer detail-drawer" [class.drawer-open]="detailDrawerOpen && activePage === 'jobs'" aria-label="Job detail drawer">
|
||||||
<div class="drawer-title"><div><span class="chip normal">ELIGIBLE</span><h2>{{ getSelectedJobShortName() }}</h2><div class="caption">{{ selectedJobClass || 'Select a job class' }}</div></div><button type="button" class="drawer-close" (click)="closeDetailDrawer()">Close</button></div>
|
<div class="drawer-title"><div><span class="chip normal">SCHEDULED</span><h2>{{ getSelectedJobShortName() }}</h2><div class="caption">{{ getSelectedJobKeyLabel() }}</div></div><button type="button" class="drawer-close" (click)="closeDetailDrawer()">Close</button></div>
|
||||||
<div class="tabs"><button type="button" class="tab active">Overview</button><button type="button" class="tab" data-roadmap="Job trigger relationships are on the roadmap">Triggers</button><button type="button" class="tab" data-roadmap="JobDataMap editing is on the roadmap">Data Map</button><button type="button" class="tab" data-roadmap="Job execution history is on the roadmap">Executions</button></div>
|
<div class="tabs"><button type="button" class="tab active">Overview</button><button type="button" class="tab" data-roadmap="Job trigger relationships are on the roadmap">Triggers</button><button type="button" class="tab" data-roadmap="JobDataMap editing is on the roadmap">Data Map</button><button type="button" class="tab" data-roadmap="Job execution history is on the roadmap">Executions</button></div>
|
||||||
<div class="field-grid">
|
<div class="field-grid">
|
||||||
<div class="field"><label>Class</label><strong>{{ getSelectedJobShortName() }}</strong></div>
|
<div class="field"><label>Class</label><strong>{{ getSelectedJobShortName() }}</strong></div>
|
||||||
<button type="button" class="field field-button" data-roadmap="Job key/group metadata is on the roadmap"><label>Group</label><strong>Roadmap</strong></button>
|
<div class="field"><label>Group</label><strong>{{ selectedScheduledJob?.jobKeyDTO?.group || '-' }}</strong></div>
|
||||||
<button type="button" class="field field-button" data-roadmap="Durability metadata is on the roadmap"><label>Durable</label><strong>Roadmap</strong></button>
|
<div class="field"><label>Durable</label><strong>{{ selectedScheduledJob?.durable ? 'YES' : 'NO' }}</strong></div>
|
||||||
<button type="button" class="field field-button" data-roadmap="Recovery metadata is on the roadmap"><label>Requests recovery</label><strong>Roadmap</strong></button>
|
<div class="field"><label>Requests recovery</label><strong>{{ selectedScheduledJob?.requestsRecovery ? 'YES' : 'NO' }}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<pre class="code-block">Backend contract
|
<pre class="code-block">Backend contract
|
||||||
GET /quartz-manager/jobs
|
GET /quartz-manager/jobs
|
||||||
Returns eligible Java job class names.</pre>
|
Returns scheduled Quartz jobs.</pre>
|
||||||
<div class="actions"><button type="button" class="btn primary" data-roadmap="Manual trigger-now by job key is on the roadmap">Trigger Now</button><button type="button" class="btn" data-roadmap="Pause job is on the roadmap">Pause</button><button type="button" class="btn" (click)="openCreateTriggerWizard(); triggerDraft.jobClass = selectedJobClass">Create SimpleTrigger</button></div>
|
<div class="actions"><button type="button" class="btn primary" (click)="triggerSelectedJobNow()">Trigger Now</button><button type="button" class="btn" data-roadmap="Pause job is on the roadmap">Pause</button><button type="button" class="btn" (click)="openCreateTriggerWizard(); triggerDraft.jobClass = selectedScheduledJob?.jobClassName || selectedJobClass">Create SimpleTrigger</button></div>
|
||||||
<div class="danger-zone"><strong>Danger zone</strong><span class="help">Interrupt and delete need backend support and explicit confirmation.</span><div class="actions"><button type="button" class="btn danger" data-roadmap="Job interruption is on the roadmap">Interrupt</button><button type="button" class="btn danger" data-roadmap="Job deletion is on the roadmap">Delete Job</button></div></div>
|
<div class="danger-zone"><strong>Danger zone</strong><span class="help">Interrupt remains roadmap-gated. Delete uses the scheduled job endpoint.</span><div class="actions"><button type="button" class="btn danger" data-roadmap="Job interruption is on the roadmap">Interrupt</button><button type="button" class="btn danger" (click)="deleteSelectedJob()">Delete Job</button></div></div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -227,11 +229,11 @@ Returns eligible Java job class names.</pre>
|
|||||||
<div class="field"><label>Final fire</label><strong>{{ formatDateTime(selectedTrigger?.finalFireTime) || 'none' }}</strong></div>
|
<div class="field"><label>Final fire</label><strong>{{ formatDateTime(selectedTrigger?.finalFireTime) || 'none' }}</strong></div>
|
||||||
<button type="button" class="field field-button" data-roadmap="Timezone metadata is on the roadmap"><label>Timezone</label><strong>Roadmap</strong></button>
|
<button type="button" class="field field-button" data-roadmap="Timezone metadata is on the roadmap"><label>Timezone</label><strong>Roadmap</strong></button>
|
||||||
<div class="field"><label>Repeat interval</label><strong>{{ selectedTrigger?.repeatInterval ? formatDuration(selectedTrigger.repeatInterval) : '-' }}</strong></div>
|
<div class="field"><label>Repeat interval</label><strong>{{ selectedTrigger?.repeatInterval ? formatDuration(selectedTrigger.repeatInterval) : '-' }}</strong></div>
|
||||||
<button type="button" class="field field-button" data-roadmap="Calendar attachment is on the roadmap"><label>Calendar</label><strong>Roadmap</strong></button>
|
<div class="field"><label>Calendar</label><strong>{{ selectedTrigger?.calendarName || 'none' }}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<section class="preview"><h4>Schedule summary</h4><div>{{ getSelectedTriggerRepeatSummary() }}. Next fire: {{ formatDateTime(selectedTrigger?.nextFireTime) || 'not available' }}.</div></section>
|
<section class="preview"><h4>Schedule summary</h4><div>{{ getSelectedTriggerRepeatSummary() }}. Next fire: {{ formatDateTime(selectedTrigger?.nextFireTime) || 'not available' }}.</div></section>
|
||||||
<div class="actions"><button type="button" class="btn" data-roadmap="Trigger pause endpoint is on the roadmap">Pause</button><button type="button" class="btn" (click)="openRescheduleWizard()">Reschedule</button><button type="button" class="btn" data-roadmap="Trigger duplication is on the roadmap">Duplicate</button></div>
|
<div class="actions"><button type="button" class="btn" (click)="pauseSelectedTrigger()">Pause</button><button type="button" class="btn" (click)="resumeSelectedTrigger()">Resume</button><button type="button" class="btn" (click)="openRescheduleWizard()">Reschedule</button><button type="button" class="btn" data-roadmap="Trigger duplication is on the roadmap">Duplicate</button></div>
|
||||||
<div class="danger-zone"><strong>Danger zone</strong><span class="help">Unschedule and reset-error require backend endpoints that are still on the roadmap.</span><div class="actions"><button type="button" class="btn danger" data-roadmap="Unschedule trigger is on the roadmap">Unschedule</button><button type="button" class="btn danger" data-roadmap="Reset error trigger is on the roadmap">Reset Error</button></div></div>
|
<div class="danger-zone"><strong>Danger zone</strong><span class="help">Unschedule uses the trigger lifecycle endpoint. Reset-error remains roadmap-gated.</span><div class="actions"><button type="button" class="btn danger" (click)="unscheduleSelectedTrigger()">Unschedule</button><button type="button" class="btn danger" data-roadmap="Reset error trigger is on the roadmap">Reset Error</button></div></div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -307,7 +309,7 @@ Returns eligible Java job class names.</pre>
|
|||||||
</section>
|
</section>
|
||||||
<section class="card span-8">
|
<section class="card span-8">
|
||||||
<div class="card-header"><h2 class="card-title">Scheduler Metadata</h2><span class="chip accent">CURRENT API</span></div>
|
<div class="card-header"><h2 class="card-title">Scheduler Metadata</h2><span class="chip accent">CURRENT API</span></div>
|
||||||
<div class="card-body summary-grid"><div class="field"><label>Scheduler name</label><strong>{{ scheduler?.name || '-' }}</strong></div><div class="field"><label>Instance ID</label><strong>{{ scheduler?.instanceId || '-' }}</strong></div><div class="field"><label>Status</label><strong>{{ scheduler?.status || '-' }}</strong></div><div class="field"><label>Trigger keys</label><strong>{{ triggerKeys.length }}</strong></div><button type="button" class="field field-button" data-roadmap="Quartz version metadata is on the roadmap"><label>Quartz version</label><strong>Roadmap</strong></button><button type="button" class="field field-button" data-roadmap="Thread pool metadata is on the roadmap"><label>Thread pool</label><strong>Roadmap</strong></button><button type="button" class="field field-button" data-roadmap="Job store metadata is on the roadmap"><label>Job store</label><strong>Roadmap</strong></button><button type="button" class="field field-button" data-roadmap="Cluster mode support is on the roadmap"><label>Clustered</label><strong>Roadmap</strong></button></div>
|
<div class="card-body summary-grid"><div class="field"><label>Scheduler name</label><strong>{{ scheduler?.name || '-' }}</strong></div><div class="field"><label>Instance ID</label><strong>{{ scheduler?.instanceId || '-' }}</strong></div><div class="field"><label>Status</label><strong>{{ scheduler?.status || '-' }}</strong></div><div class="field"><label>Trigger keys</label><strong>{{ triggerKeys.length }}</strong></div><div class="field"><label>Quartz version</label><strong>{{ scheduler?.quartzVersion || '-' }}</strong></div><div class="field"><label>Thread pool</label><strong>{{ scheduler?.threadPoolSize || '-' }}</strong></div><div class="field"><label>Job store</label><strong>{{ scheduler?.jobStoreClass || '-' }}</strong></div><div class="field"><label>Clustered</label><strong>{{ scheduler?.clustered ? 'YES' : 'NO' }}</strong></div></div>
|
||||||
</section>
|
</section>
|
||||||
<section class="card span-4">
|
<section class="card span-4">
|
||||||
<div class="card-header"><h2 class="card-title">Cluster Nodes</h2><span class="chip warn">ROADMAP</span></div>
|
<div class="card-header"><h2 class="card-title">Cluster Nodes</h2><span class="chip warn">ROADMAP</span></div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {ProgressRxWebsocketService} from '../../services/progress.rx-websocket.s
|
|||||||
import {Scheduler} from '../../model/scheduler.model';
|
import {Scheduler} from '../../model/scheduler.model';
|
||||||
import {SimpleTriggerCommand} from '../../model/simple-trigger.command';
|
import {SimpleTriggerCommand} from '../../model/simple-trigger.command';
|
||||||
import {SimpleTrigger} from '../../model/simple-trigger.model';
|
import {SimpleTrigger} from '../../model/simple-trigger.model';
|
||||||
|
import {ScheduledJob} from '../../model/scheduled-job.model';
|
||||||
import {TriggerKey} from '../../model/triggerKey.model';
|
import {TriggerKey} from '../../model/triggerKey.model';
|
||||||
import TriggerFiredBundle from '../../model/trigger-fired-bundle.model';
|
import TriggerFiredBundle from '../../model/trigger-fired-bundle.model';
|
||||||
|
|
||||||
@@ -54,7 +55,9 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
selectedTriggerKey: TriggerKey;
|
selectedTriggerKey: TriggerKey;
|
||||||
selectedTrigger: SimpleTrigger;
|
selectedTrigger: SimpleTrigger;
|
||||||
selectedJobClass: string;
|
selectedJobClass: string;
|
||||||
|
selectedScheduledJob: ScheduledJob;
|
||||||
jobs: string[] = [];
|
jobs: string[] = [];
|
||||||
|
scheduledJobs: ScheduledJob[] = [];
|
||||||
logs: ConsoleLogRecord[] = [];
|
logs: ConsoleLogRecord[] = [];
|
||||||
progress: TriggerFiredBundle;
|
progress: TriggerFiredBundle;
|
||||||
roadmapNotice: string;
|
roadmapNotice: string;
|
||||||
@@ -87,6 +90,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
this.refreshScheduler();
|
this.refreshScheduler();
|
||||||
this.fetchTriggers();
|
this.fetchTriggers();
|
||||||
this.fetchJobs();
|
this.fetchJobs();
|
||||||
|
this.fetchScheduledJobs();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@@ -179,7 +183,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standbyScheduler() {
|
standbyScheduler() {
|
||||||
const subscription = this.schedulerService.pauseScheduler().subscribe({
|
const subscription = this.schedulerService.standbyScheduler().subscribe({
|
||||||
next: () => this.setSchedulerStatus('PAUSED', 'Scheduler moved to standby.'),
|
next: () => this.setSchedulerStatus('PAUSED', 'Scheduler moved to standby.'),
|
||||||
error: () => this.operationError = 'Unable to move the scheduler to standby.'
|
error: () => this.operationError = 'Unable to move the scheduler to standby.'
|
||||||
});
|
});
|
||||||
@@ -198,7 +202,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
if (!window.confirm('Shutdown the scheduler instance?')) {
|
if (!window.confirm('Shutdown the scheduler instance?')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const subscription = this.schedulerService.stopScheduler().subscribe({
|
const subscription = this.schedulerService.shutdownScheduler().subscribe({
|
||||||
next: () => this.setSchedulerStatus('STOPPED', 'Scheduler shut down.'),
|
next: () => this.setSchedulerStatus('STOPPED', 'Scheduler shut down.'),
|
||||||
error: () => this.operationError = 'Unable to shut down the scheduler.'
|
error: () => this.operationError = 'Unable to shut down the scheduler.'
|
||||||
});
|
});
|
||||||
@@ -243,12 +247,23 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
this.subscriptions.push(subscription);
|
this.subscriptions.push(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchScheduledJobs() {
|
||||||
|
const subscription = this.jobService.fetchScheduledJobs().subscribe({
|
||||||
|
next: scheduledJobs => {
|
||||||
|
this.scheduledJobs = scheduledJobs || [];
|
||||||
|
this.selectedScheduledJob = this.scheduledJobs[0];
|
||||||
|
},
|
||||||
|
error: () => this.operationError = 'Unable to load scheduled jobs.'
|
||||||
|
});
|
||||||
|
this.subscriptions.push(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
fetchTriggerDetails(triggerKeys: TriggerKey[]) {
|
fetchTriggerDetails(triggerKeys: TriggerKey[]) {
|
||||||
triggerKeys.forEach(triggerKey => {
|
triggerKeys.forEach(triggerKey => {
|
||||||
const subscription = this.schedulerService.getSimpleTriggerConfig(triggerKey.name).subscribe({
|
const subscription = this.schedulerService.getSimpleTriggerConfig(triggerKey.name, this.getTriggerGroup(triggerKey)).subscribe({
|
||||||
next: trigger => this.triggerDetailsByName[triggerKey.name] = trigger as SimpleTrigger,
|
next: trigger => this.triggerDetailsByName[this.getTriggerDetailKey(triggerKey)] = trigger as SimpleTrigger,
|
||||||
error: () => {
|
error: () => {
|
||||||
this.triggerDetailsByName[triggerKey.name] = null;
|
this.triggerDetailsByName[this.getTriggerDetailKey(triggerKey)] = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.subscriptions.push(subscription);
|
this.subscriptions.push(subscription);
|
||||||
@@ -264,12 +279,12 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
this.openDetailDrawer();
|
this.openDetailDrawer();
|
||||||
}
|
}
|
||||||
this.triggerLoading = true;
|
this.triggerLoading = true;
|
||||||
this.selectedTrigger = this.triggerDetailsByName[triggerKey.name] || null;
|
this.selectedTrigger = this.triggerDetailsByName[this.getTriggerDetailKey(triggerKey)] || null;
|
||||||
this.subscribeToTriggerTopics(this.selectedTriggerKey);
|
this.subscribeToTriggerTopics(this.selectedTriggerKey);
|
||||||
const subscription = this.schedulerService.getSimpleTriggerConfig(triggerKey.name).subscribe({
|
const subscription = this.schedulerService.getSimpleTriggerConfig(triggerKey.name, this.getTriggerGroup(triggerKey)).subscribe({
|
||||||
next: trigger => {
|
next: trigger => {
|
||||||
this.selectedTrigger = trigger as SimpleTrigger;
|
this.selectedTrigger = trigger as SimpleTrigger;
|
||||||
this.triggerDetailsByName[triggerKey.name] = trigger as SimpleTrigger;
|
this.triggerDetailsByName[this.getTriggerDetailKey(triggerKey)] = trigger as SimpleTrigger;
|
||||||
this.triggerLoading = false;
|
this.triggerLoading = false;
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@@ -285,6 +300,88 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
this.openDetailDrawer();
|
this.openDetailDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectScheduledJob(job: ScheduledJob) {
|
||||||
|
this.selectedScheduledJob = job;
|
||||||
|
this.selectedJobClass = job?.jobClassName || this.selectedJobClass;
|
||||||
|
this.openDetailDrawer();
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerSelectedJobNow() {
|
||||||
|
if (!this.selectedScheduledJob) {
|
||||||
|
this.showRoadmapNotice('Trigger-now requires a scheduled job key returned by the backend');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subscription = this.jobService.triggerJob(this.selectedScheduledJob).subscribe({
|
||||||
|
next: () => this.operationNotice = 'Job triggered.',
|
||||||
|
error: () => this.operationError = 'Unable to trigger the selected job.'
|
||||||
|
});
|
||||||
|
this.subscriptions.push(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelectedJob() {
|
||||||
|
if (!this.selectedScheduledJob) {
|
||||||
|
this.showRoadmapNotice('Delete requires a scheduled job key returned by the backend');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!window.confirm(`Delete job ${this.selectedScheduledJob.jobKeyDTO.group}.${this.selectedScheduledJob.jobKeyDTO.name}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subscription = this.jobService.deleteJob(this.selectedScheduledJob).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.operationNotice = 'Job deleted.';
|
||||||
|
this.scheduledJobs = this.scheduledJobs.filter(job => !this.sameJob(job, this.selectedScheduledJob));
|
||||||
|
this.selectedScheduledJob = this.scheduledJobs[0];
|
||||||
|
},
|
||||||
|
error: () => this.operationError = 'Unable to delete the selected job.'
|
||||||
|
});
|
||||||
|
this.subscriptions.push(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseSelectedTrigger() {
|
||||||
|
if (!this.selectedTriggerKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subscription = this.triggerService.pauseTrigger(this.selectedTriggerKey).subscribe({
|
||||||
|
next: () => this.setSelectedTriggerState('PAUSED', 'Trigger paused.'),
|
||||||
|
error: () => this.operationError = 'Unable to pause the selected trigger.'
|
||||||
|
});
|
||||||
|
this.subscriptions.push(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeSelectedTrigger() {
|
||||||
|
if (!this.selectedTriggerKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subscription = this.triggerService.resumeTrigger(this.selectedTriggerKey).subscribe({
|
||||||
|
next: () => this.setSelectedTriggerState('NORMAL', 'Trigger resumed.'),
|
||||||
|
error: () => this.operationError = 'Unable to resume the selected trigger.'
|
||||||
|
});
|
||||||
|
this.subscriptions.push(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
unscheduleSelectedTrigger() {
|
||||||
|
if (!this.selectedTriggerKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!window.confirm(`Unschedule trigger ${this.getSelectedTriggerGroup()}.${this.selectedTriggerKey.name}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const triggerKey = {...this.selectedTriggerKey};
|
||||||
|
const subscription = this.triggerService.unscheduleTrigger(triggerKey).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.operationNotice = 'Trigger unscheduled.';
|
||||||
|
this.triggerKeys = this.triggerKeys.filter(currentTriggerKey => !this.sameTriggerKey(currentTriggerKey, triggerKey));
|
||||||
|
delete this.triggerDetailsByName[this.getTriggerDetailKey(triggerKey)];
|
||||||
|
this.selectedTriggerKey = this.triggerKeys[0];
|
||||||
|
this.selectedTrigger = this.selectedTriggerKey
|
||||||
|
? this.triggerDetailsByName[this.getTriggerDetailKey(this.selectedTriggerKey)]
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
error: () => this.operationError = 'Unable to unschedule the selected trigger.'
|
||||||
|
});
|
||||||
|
this.subscriptions.push(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
openCreateTriggerWizard() {
|
openCreateTriggerWizard() {
|
||||||
this.resetWizard();
|
this.resetWizard();
|
||||||
this.wizardOpen = true;
|
this.wizardOpen = true;
|
||||||
@@ -302,7 +399,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trigger = this.selectedTrigger || this.triggerDetailsByName[this.selectedTriggerKey.name];
|
const trigger = this.selectedTrigger || this.triggerDetailsByName[this.getTriggerDetailKey(this.selectedTriggerKey)];
|
||||||
const repeatInterval = this.splitRepeatInterval(trigger?.repeatInterval || 60000);
|
const repeatInterval = this.splitRepeatInterval(trigger?.repeatInterval || 60000);
|
||||||
this.wizardMode = 'edit';
|
this.wizardMode = 'edit';
|
||||||
this.wizardOpen = true;
|
this.wizardOpen = true;
|
||||||
@@ -337,6 +434,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const command = new SimpleTriggerCommand();
|
const command = new SimpleTriggerCommand();
|
||||||
command.triggerName = this.triggerDraft.triggerName.trim();
|
command.triggerName = this.triggerDraft.triggerName.trim();
|
||||||
|
command.triggerGroup = this.triggerDraft.group || 'DEFAULT';
|
||||||
command.jobClass = this.triggerDraft.jobClass;
|
command.jobClass = this.triggerDraft.jobClass;
|
||||||
command.startDate = this.fromDatetimeLocalValue(this.triggerDraft.startDate);
|
command.startDate = this.fromDatetimeLocalValue(this.triggerDraft.startDate);
|
||||||
command.endDate = this.fromDatetimeLocalValue(this.triggerDraft.endDate);
|
command.endDate = this.fromDatetimeLocalValue(this.triggerDraft.endDate);
|
||||||
@@ -352,7 +450,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
const subscription = request.subscribe({
|
const subscription = request.subscribe({
|
||||||
next: trigger => {
|
next: trigger => {
|
||||||
this.wizardSubmitting = false;
|
this.wizardSubmitting = false;
|
||||||
this.triggerDetailsByName[trigger.triggerKeyDTO.name] = trigger as SimpleTrigger;
|
this.triggerDetailsByName[this.getTriggerDetailKey(trigger.triggerKeyDTO)] = trigger as SimpleTrigger;
|
||||||
this.upsertTriggerKey(trigger.triggerKeyDTO);
|
this.upsertTriggerKey(trigger.triggerKeyDTO);
|
||||||
this.selectTrigger(trigger.triggerKeyDTO);
|
this.selectTrigger(trigger.triggerKeyDTO);
|
||||||
this.wizardOpen = false;
|
this.wizardOpen = false;
|
||||||
@@ -380,7 +478,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTriggerDetail(triggerKey: TriggerKey): SimpleTrigger {
|
getTriggerDetail(triggerKey: TriggerKey): SimpleTrigger {
|
||||||
return triggerKey?.name ? this.triggerDetailsByName[triggerKey.name] : null;
|
return triggerKey?.name ? this.triggerDetailsByName[this.getTriggerDetailKey(triggerKey)] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerGroup(triggerKey: TriggerKey): string {
|
getTriggerGroup(triggerKey: TriggerKey): string {
|
||||||
@@ -388,7 +486,7 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTriggerType(triggerKey: TriggerKey): string {
|
getTriggerType(triggerKey: TriggerKey): string {
|
||||||
return this.getTriggerDetail(triggerKey) ? 'SimpleTrigger' : 'SimpleTrigger';
|
return this.getTriggerDetail(triggerKey)?.type || 'SimpleTrigger';
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerState(triggerKey: TriggerKey): string {
|
getTriggerState(triggerKey: TriggerKey): string {
|
||||||
@@ -396,6 +494,9 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
if (!trigger) {
|
if (!trigger) {
|
||||||
return 'UNKNOWN';
|
return 'UNKNOWN';
|
||||||
}
|
}
|
||||||
|
if (trigger.state) {
|
||||||
|
return trigger.state;
|
||||||
|
}
|
||||||
if (!trigger.mayFireAgain) {
|
if (!trigger.mayFireAgain) {
|
||||||
return 'COMPLETE';
|
return 'COMPLETE';
|
||||||
}
|
}
|
||||||
@@ -421,7 +522,10 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
getTriggerJobName(triggerKey: TriggerKey): string {
|
getTriggerJobName(triggerKey: TriggerKey): string {
|
||||||
const trigger = this.getTriggerDetail(triggerKey);
|
const trigger = this.getTriggerDetail(triggerKey);
|
||||||
return trigger?.jobKeyDTO?.name || this.shortClassName(trigger?.jobDetailDTO?.jobClassName) || 'Roadmap';
|
const jobGroup = trigger?.jobKeyDTO?.group ? `${trigger.jobKeyDTO.group}.` : '';
|
||||||
|
return trigger?.jobKeyDTO?.name
|
||||||
|
? `${jobGroup}${trigger.jobKeyDTO.name}`
|
||||||
|
: this.shortClassName(trigger?.jobDetailDTO?.jobClassName) || 'Roadmap';
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerNextFireLabel(triggerKey: TriggerKey): string {
|
getTriggerNextFireLabel(triggerKey: TriggerKey): string {
|
||||||
@@ -481,7 +585,18 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSelectedJobShortName(): string {
|
getSelectedJobShortName(): string {
|
||||||
return this.shortClassName(this.selectedJobClass) || '-';
|
return this.shortClassName(this.selectedScheduledJob?.jobClassName || this.selectedJobClass) || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedJobKeyLabel(): string {
|
||||||
|
if (!this.selectedScheduledJob?.jobKeyDTO) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return `${this.selectedScheduledJob.jobKeyDTO.group}.${this.selectedScheduledJob.jobKeyDTO.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScheduledJobRows(): ScheduledJob[] {
|
||||||
|
return this.scheduledJobs || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getWizardTitle(): string {
|
getWizardTitle(): string {
|
||||||
@@ -561,6 +676,16 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
this.roadmapNotice = null;
|
this.roadmapNotice = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setSelectedTriggerState(state: string, notice: string) {
|
||||||
|
if (this.selectedTrigger) {
|
||||||
|
this.selectedTrigger.state = state;
|
||||||
|
}
|
||||||
|
if (this.selectedTriggerKey?.name && this.triggerDetailsByName[this.getTriggerDetailKey(this.selectedTriggerKey)]) {
|
||||||
|
this.triggerDetailsByName[this.getTriggerDetailKey(this.selectedTriggerKey)].state = state;
|
||||||
|
}
|
||||||
|
this.operationNotice = notice;
|
||||||
|
}
|
||||||
|
|
||||||
private subscribeToTriggerTopics(triggerKey: TriggerKey) {
|
private subscribeToTriggerTopics(triggerKey: TriggerKey) {
|
||||||
this.unsubscribeFromTriggerTopics();
|
this.unsubscribeFromTriggerTopics();
|
||||||
this.logs = [];
|
this.logs = [];
|
||||||
@@ -598,11 +723,23 @@ export class ManagerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private upsertTriggerKey(triggerKey: TriggerKey) {
|
private upsertTriggerKey(triggerKey: TriggerKey) {
|
||||||
if (!this.triggerKeys.some(currentTriggerKey => currentTriggerKey.name === triggerKey.name)) {
|
if (!this.triggerKeys.some(currentTriggerKey => this.sameTriggerKey(currentTriggerKey, triggerKey))) {
|
||||||
this.triggerKeys = [triggerKey, ...this.triggerKeys];
|
this.triggerKeys = [triggerKey, ...this.triggerKeys];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sameTriggerKey(first: TriggerKey, second: TriggerKey): boolean {
|
||||||
|
return first?.name === second?.name && this.getTriggerGroup(first) === this.getTriggerGroup(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTriggerDetailKey(triggerKey: TriggerKey): string {
|
||||||
|
return `${this.getTriggerGroup(triggerKey)}.${triggerKey.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sameJob(first: ScheduledJob, second: ScheduledJob): boolean {
|
||||||
|
return first?.jobKeyDTO?.name === second?.jobKeyDTO?.name && first?.jobKeyDTO?.group === second?.jobKeyDTO?.group;
|
||||||
|
}
|
||||||
|
|
||||||
private buildEmptyDraft(): TriggerDraft {
|
private buildEmptyDraft(): TriggerDraft {
|
||||||
return {
|
return {
|
||||||
triggerName: '',
|
triggerName: '',
|
||||||
|
|||||||
@@ -8,26 +8,35 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import it.fabioformosa.quartzmanager.api.common.config.OpenAPIConfigConsts;
|
import it.fabioformosa.quartzmanager.api.common.config.OpenAPIConfigConsts;
|
||||||
import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
|
import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.ScheduledJobDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.JobNotFoundException;
|
||||||
import it.fabioformosa.quartzmanager.api.services.JobService;
|
import it.fabioformosa.quartzmanager.api.services.JobService;
|
||||||
|
import org.quartz.SchedulerException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RequestMapping(JobController.JOB_CONTROLLER_BASE_URL)
|
@RequestMapping(QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH)
|
||||||
@SecurityRequirement(name = OpenAPIConfigConsts.QUARTZ_MANAGER_SEC_OAS_SCHEMA)
|
@SecurityRequirement(name = OpenAPIConfigConsts.QUARTZ_MANAGER_SEC_OAS_SCHEMA)
|
||||||
@RestController
|
@RestController
|
||||||
public class JobController {
|
public class JobController {
|
||||||
public static final String JOB_CONTROLLER_BASE_URL = QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/jobs";
|
public static final String JOB_CONTROLLER_BASE_URL = QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/jobs";
|
||||||
|
public static final String JOB_CLASSES_CONTROLLER_BASE_URL = QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/job-classes";
|
||||||
private final JobService jobService;
|
private final JobService jobService;
|
||||||
|
|
||||||
public JobController(JobService jobService) {
|
public JobController(JobService jobService) {
|
||||||
this.jobService = jobService;
|
this.jobService = jobService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping("/job-classes")
|
||||||
@Operation(summary = "Get the list of job classes eligible for Quartz-Manager")
|
@Operation(summary = "Get the list of job classes eligible for Quartz-Manager")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "200", description = "Return a list of qualified java classes",
|
@ApiResponse(responseCode = "200", description = "Return a list of qualified java classes",
|
||||||
@@ -38,4 +47,35 @@ public class JobController {
|
|||||||
return jobService.getJobClasses().stream().map(Class::getName).collect(Collectors.toList());
|
return jobService.getJobClasses().stream().map(Class::getName).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/jobs")
|
||||||
|
@Operation(summary = "Get the list of scheduled jobs")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Return a list of scheduled jobs",
|
||||||
|
content = {@Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = ScheduledJobDTO.class))})
|
||||||
|
})
|
||||||
|
public List<ScheduledJobDTO> listScheduledJobs() throws SchedulerException {
|
||||||
|
return jobService.fetchScheduledJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/jobs/{group}/{name}")
|
||||||
|
@Operation(summary = "Get a scheduled job")
|
||||||
|
public ScheduledJobDTO getScheduledJob(@PathVariable String group, @PathVariable String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
return jobService.getScheduledJob(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/jobs/{group}/{name}/trigger")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@Operation(summary = "Trigger a job now")
|
||||||
|
public void triggerJob(@PathVariable String group, @PathVariable String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
jobService.triggerJob(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/jobs/{group}/{name}")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@Operation(summary = "Delete a job")
|
||||||
|
public void deleteJob(@PathVariable String group, @PathVariable String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
jobService.deleteJob(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -51,21 +52,21 @@ public class SchedulerController {
|
|||||||
return schedulerService.getScheduler();
|
return schedulerService.getScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/pause")
|
@PostMapping("/standby")
|
||||||
@Operation(summary = "Get paused the scheduler")
|
@Operation(summary = "Put the scheduler in standby mode")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "204", description = "Got paused successfully")
|
@ApiResponse(responseCode = "204", description = "Scheduler moved to standby successfully")
|
||||||
})
|
})
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
public void pause() throws SchedulerException {
|
public void standby() throws SchedulerException {
|
||||||
log.info("SCHEDULER - PAUSE COMMAND");
|
log.info("SCHEDULER - STANDBY COMMAND");
|
||||||
schedulerService.standby();
|
schedulerService.standby();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/resume")
|
@PostMapping("/resume")
|
||||||
@Operation(summary = "Get resumed the scheduler")
|
@Operation(summary = "Resume the scheduler from standby mode")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "204", description = "Got resumed successfully")
|
@ApiResponse(responseCode = "204", description = "Scheduler resumed successfully")
|
||||||
})
|
})
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
public void resume() throws SchedulerException {
|
public void resume() throws SchedulerException {
|
||||||
@@ -73,25 +74,25 @@ public class SchedulerController {
|
|||||||
schedulerService.start();
|
schedulerService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/run")
|
@PostMapping("/start")
|
||||||
@Operation(summary = "Start the scheduler")
|
@Operation(summary = "Start the scheduler")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "204", description = "Got started successfully")
|
@ApiResponse(responseCode = "204", description = "Scheduler started successfully")
|
||||||
})
|
})
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
public void run() throws SchedulerException {
|
public void start() throws SchedulerException {
|
||||||
log.info("SCHEDULER - START COMMAND");
|
log.info("SCHEDULER - START COMMAND");
|
||||||
schedulerService.start();
|
schedulerService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/stop")
|
@PostMapping("/shutdown")
|
||||||
@Operation(summary = "Stop the scheduler")
|
@Operation(summary = "Shutdown the scheduler terminally")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "204", description = "Got stopped successfully")
|
@ApiResponse(responseCode = "204", description = "Scheduler shut down successfully")
|
||||||
})
|
})
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
public void stop() throws SchedulerException {
|
public void shutdown() throws SchedulerException {
|
||||||
log.info("SCHEDULER - STOP COMMAND");
|
log.info("SCHEDULER - SHUTDOWN COMMAND");
|
||||||
schedulerService.shutdown();
|
schedulerService.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class SimpleTriggerController {
|
|||||||
this.simpleSchedulerService = simpleSchedulerService;
|
this.simpleSchedulerService = simpleSchedulerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{name}")
|
@GetMapping("/{group}/{name}")
|
||||||
@Operation(summary = "Get a simple trigger by name")
|
@Operation(summary = "Get a simple trigger by name")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "200", description = "Got the trigger by its name",
|
@ApiResponse(responseCode = "200", description = "Got the trigger by its name",
|
||||||
@@ -44,11 +44,11 @@ public class SimpleTriggerController {
|
|||||||
@ApiResponse(responseCode = "404", description = "Trigger not found",
|
@ApiResponse(responseCode = "404", description = "Trigger not found",
|
||||||
content = @Content)
|
content = @Content)
|
||||||
})
|
})
|
||||||
public SimpleTriggerDTO getSimpleTrigger(@PathVariable String name) throws SchedulerException, TriggerNotFoundException {
|
public SimpleTriggerDTO getSimpleTrigger(@PathVariable String group, @PathVariable String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
return simpleSchedulerService.getSimpleTriggerByName(name);
|
return simpleSchedulerService.getSimpleTrigger(group, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{name}")
|
@PostMapping("/{group}/{name}")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
@Operation(summary = "Schedule a new simple trigger")
|
@Operation(summary = "Schedule a new simple trigger")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@@ -58,10 +58,11 @@ public class SimpleTriggerController {
|
|||||||
@ApiResponse(responseCode = "400", description = "Invalid trigger configuration",
|
@ApiResponse(responseCode = "400", description = "Invalid trigger configuration",
|
||||||
content = @Content)
|
content = @Content)
|
||||||
})
|
})
|
||||||
public SimpleTriggerDTO postSimpleTrigger(@PathVariable String name, @Valid @RequestBody SimpleTriggerInputDTO simpleTriggerInputDTO) throws SchedulerException, ClassNotFoundException {
|
public SimpleTriggerDTO postSimpleTrigger(@PathVariable String group, @PathVariable String name, @Valid @RequestBody SimpleTriggerInputDTO simpleTriggerInputDTO) throws SchedulerException, ClassNotFoundException {
|
||||||
log.info("SIMPLE TRIGGER - CREATING a SimpleTrigger {} {}", name, simpleTriggerInputDTO);
|
log.info("SIMPLE TRIGGER - CREATING a SimpleTrigger {} {}", name, simpleTriggerInputDTO);
|
||||||
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
||||||
.triggerName(name)
|
.triggerName(name)
|
||||||
|
.triggerGroup(group)
|
||||||
.simpleTriggerInputDTO(simpleTriggerInputDTO)
|
.simpleTriggerInputDTO(simpleTriggerInputDTO)
|
||||||
.build();
|
.build();
|
||||||
SimpleTriggerDTO newTriggerDTO = simpleSchedulerService.scheduleSimpleTrigger(simpleTriggerCommandDTO);
|
SimpleTriggerDTO newTriggerDTO = simpleSchedulerService.scheduleSimpleTrigger(simpleTriggerCommandDTO);
|
||||||
@@ -69,7 +70,7 @@ public class SimpleTriggerController {
|
|||||||
return newTriggerDTO;
|
return newTriggerDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{name}")
|
@PutMapping("/{group}/{name}")
|
||||||
@Operation(summary = "Reschedule a simple trigger")
|
@Operation(summary = "Reschedule a simple trigger")
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "200", description = "Rescheduled a simple trigger",
|
@ApiResponse(responseCode = "200", description = "Rescheduled a simple trigger",
|
||||||
@@ -78,10 +79,11 @@ public class SimpleTriggerController {
|
|||||||
@ApiResponse(responseCode = "400", description = "Invalid trigger configuration",
|
@ApiResponse(responseCode = "400", description = "Invalid trigger configuration",
|
||||||
content = @Content)
|
content = @Content)
|
||||||
})
|
})
|
||||||
public TriggerDTO rescheduleSimpleTrigger(@PathVariable String name, @Valid @RequestBody SimpleTriggerInputDTO simpleTriggerInputDTO) throws SchedulerException {
|
public TriggerDTO rescheduleSimpleTrigger(@PathVariable String group, @PathVariable String name, @Valid @RequestBody SimpleTriggerInputDTO simpleTriggerInputDTO) throws SchedulerException, TriggerNotFoundException {
|
||||||
log.info("SIMPLE TRIGGER - RESCHEDULING the trigger {} {}", name, simpleTriggerInputDTO);
|
log.info("SIMPLE TRIGGER - RESCHEDULING the trigger {} {}", name, simpleTriggerInputDTO);
|
||||||
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
||||||
.triggerName(name)
|
.triggerName(name)
|
||||||
|
.triggerGroup(group)
|
||||||
.simpleTriggerInputDTO(simpleTriggerInputDTO)
|
.simpleTriggerInputDTO(simpleTriggerInputDTO)
|
||||||
.build();
|
.build();
|
||||||
TriggerDTO triggerDTO = simpleSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO);
|
TriggerDTO triggerDTO = simpleSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO);
|
||||||
|
|||||||
@@ -7,11 +7,18 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
|
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.TriggerDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
|
||||||
import it.fabioformosa.quartzmanager.api.services.TriggerService;
|
import it.fabioformosa.quartzmanager.api.services.TriggerService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -44,4 +51,37 @@ public class TriggerController {
|
|||||||
return triggerService.fetchTriggers();
|
return triggerService.fetchTriggers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{group}/{name}")
|
||||||
|
@Operation(summary = "Get trigger details")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Got trigger details",
|
||||||
|
content = { @Content(mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = TriggerDTO.class)) }),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Trigger not found", content = @Content)
|
||||||
|
})
|
||||||
|
public TriggerDTO getTrigger(@PathVariable String group, @PathVariable String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
return triggerService.getTrigger(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{group}/{name}/pause")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@Operation(summary = "Pause a trigger")
|
||||||
|
public void pauseTrigger(@PathVariable String group, @PathVariable String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
triggerService.pauseTrigger(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{group}/{name}/resume")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@Operation(summary = "Resume a trigger")
|
||||||
|
public void resumeTrigger(@PathVariable String group, @PathVariable String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
triggerService.resumeTrigger(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{group}/{name}")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@Operation(summary = "Unschedule a trigger")
|
||||||
|
public void unscheduleTrigger(@PathVariable String group, @PathVariable String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
triggerService.unscheduleTrigger(group, name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package it.fabioformosa.quartzmanager.api.controllers.advices;
|
package it.fabioformosa.quartzmanager.api.controllers.advices;
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.api.exceptions.ExceptionResponse;
|
import it.fabioformosa.quartzmanager.api.exceptions.ExceptionResponse;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.JobNotFoundException;
|
||||||
import it.fabioformosa.quartzmanager.api.exceptions.ResourceConflictException;
|
import it.fabioformosa.quartzmanager.api.exceptions.ResourceConflictException;
|
||||||
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
|
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.UnsupportedTriggerTypeException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
@@ -28,4 +30,20 @@ public class ExceptionHandlingController {
|
|||||||
return ExceptionResponse.builder().errorCode(HttpStatus.NOT_FOUND.toString()).errorMessage(ex.getMessage()).build();
|
return ExceptionResponse.builder().errorCode(HttpStatus.NOT_FOUND.toString()).errorMessage(ex.getMessage()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(JobNotFoundException.class)
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
@ResponseBody
|
||||||
|
public ExceptionResponse jobNotFound(JobNotFoundException ex){
|
||||||
|
return ExceptionResponse.builder().errorCode(HttpStatus.NOT_FOUND.toString()).errorMessage(ex.getMessage()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(UnsupportedTriggerTypeException.class)
|
||||||
|
public ResponseEntity<ExceptionResponse> unsupportedTriggerType(UnsupportedTriggerTypeException ex) {
|
||||||
|
ExceptionResponse response = ExceptionResponse.builder()
|
||||||
|
.errorCode(HttpStatus.CONFLICT.toString())
|
||||||
|
.errorMessage(ex.getMessage())
|
||||||
|
.build();
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import it.fabioformosa.quartzmanager.api.enums.SchedulerStatus;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.quartz.Scheduler;
|
import org.quartz.Scheduler;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
|
import org.quartz.SchedulerMetaData;
|
||||||
import org.quartz.impl.matchers.GroupMatcher;
|
import org.quartz.impl.matchers.GroupMatcher;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SchedulerToSchedulerDTO extends AbstractBaseConverterToDTO<Scheduler, SchedulerDTO> {
|
public class SchedulerToSchedulerDTO extends AbstractBaseConverterToDTO<Scheduler, SchedulerDTO> {
|
||||||
|
|
||||||
@@ -20,6 +24,16 @@ public class SchedulerToSchedulerDTO extends AbstractBaseConverterToDTO<Schedule
|
|||||||
if(!source.isShutdown())
|
if(!source.isShutdown())
|
||||||
target.setTriggerKeys(source.getTriggerKeys(GroupMatcher.anyTriggerGroup()));
|
target.setTriggerKeys(source.getTriggerKeys(GroupMatcher.anyTriggerGroup()));
|
||||||
target.setStatus(buildTheSchedulerStatus(source));
|
target.setStatus(buildTheSchedulerStatus(source));
|
||||||
|
SchedulerMetaData metaData = source.getMetaData();
|
||||||
|
target.setQuartzVersion(metaData.getVersion());
|
||||||
|
target.setJobStoreClass(metaData.getJobStoreClass().getName());
|
||||||
|
target.setJobStoreSupportsPersistence(metaData.isJobStoreSupportsPersistence());
|
||||||
|
target.setClustered(metaData.isJobStoreClustered());
|
||||||
|
target.setThreadPoolClass(metaData.getThreadPoolClass().getName());
|
||||||
|
target.setThreadPoolSize(metaData.getThreadPoolSize());
|
||||||
|
target.setNumberOfJobsExecuted(metaData.getNumberOfJobsExecuted());
|
||||||
|
if (metaData.getRunningSince() != null)
|
||||||
|
target.setRunningSince(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(metaData.getRunningSince().toInstant().atOffset(ZoneOffset.UTC)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SchedulerStatus buildTheSchedulerStatus(Scheduler scheduler) throws SchedulerException {
|
private SchedulerStatus buildTheSchedulerStatus(Scheduler scheduler) throws SchedulerException {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class SimpleTriggerCommandDTOToSimpleTrigger implements Converter<SimpleT
|
|||||||
return triggerTriggerBuilder.withSchedule(
|
return triggerTriggerBuilder.withSchedule(
|
||||||
scheduleBuilder
|
scheduleBuilder
|
||||||
)
|
)
|
||||||
.withIdentity(triggerCommandDTO.getTriggerName()).build();
|
.withIdentity(triggerCommandDTO.getTriggerName(), triggerCommandDTO.getTriggerGroup()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setTheMisfireInstruction(SimpleTriggerCommandDTO triggerCommandDTO, SimpleScheduleBuilder scheduleBuilder) {
|
private static void setTheMisfireInstruction(SimpleTriggerCommandDTO triggerCommandDTO, SimpleScheduleBuilder scheduleBuilder) {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public class TriggerToTriggerDTO<S extends Trigger, T extends TriggerDTO> extend
|
|||||||
target.setFinalFireTime(source.getFinalFireTime());
|
target.setFinalFireTime(source.getFinalFireTime());
|
||||||
target.setMisfireInstruction(source.getMisfireInstruction());
|
target.setMisfireInstruction(source.getMisfireInstruction());
|
||||||
target.setNextFireTime(source.getNextFireTime());
|
target.setNextFireTime(source.getNextFireTime());
|
||||||
|
target.setPreviousFireTime(source.getPreviousFireTime());
|
||||||
|
target.setCalendarName(source.getCalendarName());
|
||||||
|
target.setType(source.getClass().getSimpleName());
|
||||||
target.setPriority(source.getPriority());
|
target.setPriority(source.getPriority());
|
||||||
target.setMayFireAgain(source.mayFireAgain());
|
target.setMayFireAgain(source.mayFireAgain());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.api.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class ScheduledJobDTO {
|
||||||
|
private JobKeyDTO jobKeyDTO;
|
||||||
|
private String jobClassName;
|
||||||
|
private String description;
|
||||||
|
private boolean durable;
|
||||||
|
private boolean requestsRecovery;
|
||||||
|
private Map<String, ?> jobDataMap;
|
||||||
|
private List<TriggerKeyDTO> triggerKeys;
|
||||||
|
}
|
||||||
@@ -18,4 +18,12 @@ public class SchedulerDTO {
|
|||||||
private String instanceId;
|
private String instanceId;
|
||||||
private SchedulerStatus status;
|
private SchedulerStatus status;
|
||||||
private Set<TriggerKey> triggerKeys;
|
private Set<TriggerKey> triggerKeys;
|
||||||
|
private String quartzVersion;
|
||||||
|
private String jobStoreClass;
|
||||||
|
private boolean jobStoreSupportsPersistence;
|
||||||
|
private boolean clustered;
|
||||||
|
private String threadPoolClass;
|
||||||
|
private int threadPoolSize;
|
||||||
|
private String runningSince;
|
||||||
|
private int numberOfJobsExecuted;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ import lombok.*;
|
|||||||
@ToString
|
@ToString
|
||||||
public class SimpleTriggerCommandDTO {
|
public class SimpleTriggerCommandDTO {
|
||||||
private String triggerName;
|
private String triggerName;
|
||||||
|
private String triggerGroup;
|
||||||
private SimpleTriggerInputDTO simpleTriggerInputDTO;
|
private SimpleTriggerInputDTO simpleTriggerInputDTO;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ public class TriggerDTO {
|
|||||||
private Date finalFireTime;
|
private Date finalFireTime;
|
||||||
private int misfireInstruction;
|
private int misfireInstruction;
|
||||||
private Date nextFireTime;
|
private Date nextFireTime;
|
||||||
|
private Date previousFireTime;
|
||||||
|
private String type;
|
||||||
|
private String state;
|
||||||
|
private String calendarName;
|
||||||
private JobKeyDTO jobKeyDTO;
|
private JobKeyDTO jobKeyDTO;
|
||||||
private JobDetailDTO jobDetailDTO;
|
private JobDetailDTO jobDetailDTO;
|
||||||
private boolean mayFireAgain;
|
private boolean mayFireAgain;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.api.exceptions;
|
||||||
|
|
||||||
|
public class JobNotFoundException extends Exception {
|
||||||
|
public JobNotFoundException(String group, String name) {
|
||||||
|
super("Job " + group + "." + name + " not found!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,8 @@ public class ResourceConflictException extends RuntimeException {
|
|||||||
super("Conflict on resourceID " + resourceId + " " + message);
|
super("Conflict on resourceID " + resourceId + " " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceConflictException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,8 @@ public class TriggerNotFoundException extends Exception {
|
|||||||
public TriggerNotFoundException(String name) {
|
public TriggerNotFoundException(String name) {
|
||||||
super("Trigger with name " + name + " not found!");
|
super("Trigger with name " + name + " not found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TriggerNotFoundException(String group, String name) {
|
||||||
|
super("Trigger " + group + "." + name + " not found!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package it.fabioformosa.quartzmanager.api.exceptions;
|
||||||
|
|
||||||
|
public class UnsupportedTriggerTypeException extends RuntimeException {
|
||||||
|
public UnsupportedTriggerTypeException(String group, String name) {
|
||||||
|
super("Trigger " + group + "." + name + " is not a SimpleTrigger");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
package it.fabioformosa.quartzmanager.api.services;
|
package it.fabioformosa.quartzmanager.api.services;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.JobKeyDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.ScheduledJobDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.JobNotFoundException;
|
||||||
import it.fabioformosa.quartzmanager.api.jobs.AbstractQuartzManagerJob;
|
import it.fabioformosa.quartzmanager.api.jobs.AbstractQuartzManagerJob;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.quartz.JobDetail;
|
||||||
|
import org.quartz.JobKey;
|
||||||
|
import org.quartz.Scheduler;
|
||||||
|
import org.quartz.SchedulerException;
|
||||||
|
import org.quartz.Trigger;
|
||||||
|
import org.quartz.impl.matchers.GroupMatcher;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@@ -20,8 +33,19 @@ public class JobService {
|
|||||||
private List<Class<? extends AbstractQuartzManagerJob>> jobClasses = new ArrayList<>();
|
private List<Class<? extends AbstractQuartzManagerJob>> jobClasses = new ArrayList<>();
|
||||||
|
|
||||||
private List<String> jobClassPackages = new ArrayList<>();
|
private List<String> jobClassPackages = new ArrayList<>();
|
||||||
|
private final Scheduler scheduler;
|
||||||
|
private final ConversionService conversionService;
|
||||||
|
|
||||||
public JobService(@Value("${quartz-manager.jobClassPackages}") String jobClassPackages) {
|
public JobService(String jobClassPackages) {
|
||||||
|
this(jobClassPackages, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public JobService(@Value("${quartz-manager.jobClassPackages}") String jobClassPackages,
|
||||||
|
@Qualifier("quartzManagerScheduler") Scheduler scheduler,
|
||||||
|
ConversionService conversionService) {
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.conversionService = conversionService;
|
||||||
List<String> splitPackages = Arrays.stream(Optional.of(jobClassPackages).map(str -> str.split(","))
|
List<String> splitPackages = Arrays.stream(Optional.of(jobClassPackages).map(str -> str.split(","))
|
||||||
.orElseThrow(() -> new RuntimeException("The prop quartz-manager.jobClassPackages cannot be blank!")))
|
.orElseThrow(() -> new RuntimeException("The prop quartz-manager.jobClassPackages cannot be blank!")))
|
||||||
.map(String::trim)
|
.map(String::trim)
|
||||||
@@ -47,4 +71,54 @@ public class JobService {
|
|||||||
return reflections.getSubTypesOf(AbstractQuartzManagerJob.class);
|
return reflections.getSubTypesOf(AbstractQuartzManagerJob.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ScheduledJobDTO> fetchScheduledJobs() throws SchedulerException {
|
||||||
|
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
|
||||||
|
return jobKeys.stream().map(this::convertJob).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledJobDTO getScheduledJob(String group, String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
JobKey jobKey = JobKey.jobKey(name, group);
|
||||||
|
if (!scheduler.checkExists(jobKey))
|
||||||
|
throw new JobNotFoundException(group, name);
|
||||||
|
return convertJob(jobKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void triggerJob(String group, String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
JobKey jobKey = requireJob(group, name);
|
||||||
|
scheduler.triggerJob(jobKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteJob(String group, String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
JobKey jobKey = requireJob(group, name);
|
||||||
|
scheduler.deleteJob(jobKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobKey requireJob(String group, String name) throws SchedulerException, JobNotFoundException {
|
||||||
|
JobKey jobKey = JobKey.jobKey(name, group);
|
||||||
|
if (!scheduler.checkExists(jobKey))
|
||||||
|
throw new JobNotFoundException(group, name);
|
||||||
|
return jobKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledJobDTO convertJob(JobKey jobKey) {
|
||||||
|
try {
|
||||||
|
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
||||||
|
List<TriggerKeyDTO> triggerKeys = scheduler.getTriggersOfJob(jobKey).stream()
|
||||||
|
.map(Trigger::getKey)
|
||||||
|
.map(triggerKey -> conversionService.convert(triggerKey, TriggerKeyDTO.class))
|
||||||
|
.toList();
|
||||||
|
return ScheduledJobDTO.builder()
|
||||||
|
.jobKeyDTO(conversionService.convert(jobKey, JobKeyDTO.class))
|
||||||
|
.jobClassName(jobDetail.getJobClass().getName())
|
||||||
|
.description(jobDetail.getDescription())
|
||||||
|
.durable(jobDetail.isDurable())
|
||||||
|
.requestsRecovery(jobDetail.requestsRecovery())
|
||||||
|
.jobDataMap(jobDetail.getJobDataMap())
|
||||||
|
.triggerKeys(triggerKeys)
|
||||||
|
.build();
|
||||||
|
} catch (SchedulerException ex) {
|
||||||
|
throw new IllegalStateException("Unable to read job " + jobKey, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package it.fabioformosa.quartzmanager.api.services;
|
|||||||
|
|
||||||
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerCommandDTO;
|
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerCommandDTO;
|
||||||
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerDTO;
|
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.ResourceConflictException;
|
||||||
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
|
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.UnsupportedTriggerTypeException;
|
||||||
import org.quartz.*;
|
import org.quartz.*;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
@@ -16,11 +18,25 @@ public class SimpleTriggerService extends AbstractSchedulerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SimpleTriggerDTO getSimpleTriggerByName(String name) throws SchedulerException, TriggerNotFoundException {
|
public SimpleTriggerDTO getSimpleTriggerByName(String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
Trigger trigger = getTriggerByName(name);
|
return getSimpleTrigger("DEFAULT", name);
|
||||||
return conversionService.convert(trigger, SimpleTriggerDTO.class);
|
}
|
||||||
|
|
||||||
|
public SimpleTriggerDTO getSimpleTrigger(String group, String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(name, group));
|
||||||
|
if (trigger == null)
|
||||||
|
throw new TriggerNotFoundException(group, name);
|
||||||
|
if (!(trigger instanceof SimpleTrigger simpleTrigger))
|
||||||
|
throw new UnsupportedTriggerTypeException(group, name);
|
||||||
|
SimpleTriggerDTO simpleTriggerDTO = conversionService.convert(simpleTrigger, SimpleTriggerDTO.class);
|
||||||
|
simpleTriggerDTO.setState(scheduler.getTriggerState(simpleTrigger.getKey()).name());
|
||||||
|
return simpleTriggerDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleTriggerDTO scheduleSimpleTrigger(SimpleTriggerCommandDTO simpleTriggerCommandDTO) throws SchedulerException, ClassNotFoundException {
|
public SimpleTriggerDTO scheduleSimpleTrigger(SimpleTriggerCommandDTO simpleTriggerCommandDTO) throws SchedulerException, ClassNotFoundException {
|
||||||
|
TriggerKey triggerKey = TriggerKey.triggerKey(simpleTriggerCommandDTO.getTriggerName(), simpleTriggerCommandDTO.getTriggerGroup());
|
||||||
|
if (scheduler.checkExists(triggerKey))
|
||||||
|
throw new ResourceConflictException("Trigger " + triggerKey + " already exists");
|
||||||
|
|
||||||
Class<? extends Job> jobClass = Class.forName(simpleTriggerCommandDTO.getSimpleTriggerInputDTO().getJobClass()).asSubclass(Job.class);
|
Class<? extends Job> jobClass = Class.forName(simpleTriggerCommandDTO.getSimpleTriggerInputDTO().getJobClass()).asSubclass(Job.class);
|
||||||
JobDetail jobDetail = JobBuilder.newJob()
|
JobDetail jobDetail = JobBuilder.newJob()
|
||||||
.ofType(jobClass)
|
.ofType(jobClass)
|
||||||
@@ -33,10 +49,17 @@ public class SimpleTriggerService extends AbstractSchedulerService {
|
|||||||
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
|
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleTriggerDTO rescheduleSimpleTrigger(SimpleTriggerCommandDTO triggerCommandDTO) throws SchedulerException {
|
public SimpleTriggerDTO rescheduleSimpleTrigger(SimpleTriggerCommandDTO triggerCommandDTO) throws SchedulerException, TriggerNotFoundException {
|
||||||
SimpleTrigger newSimpleTrigger = conversionService.convert(triggerCommandDTO, SimpleTrigger.class);
|
TriggerKey triggerKey = TriggerKey.triggerKey(triggerCommandDTO.getTriggerName(), triggerCommandDTO.getTriggerGroup());
|
||||||
|
Trigger existingTrigger = scheduler.getTrigger(triggerKey);
|
||||||
|
if (existingTrigger == null)
|
||||||
|
throw new TriggerNotFoundException(triggerCommandDTO.getTriggerGroup(), triggerCommandDTO.getTriggerName());
|
||||||
|
|
||||||
|
SimpleTrigger newSimpleTrigger = conversionService.convert(triggerCommandDTO, SimpleTrigger.class);
|
||||||
|
newSimpleTrigger = newSimpleTrigger.getTriggerBuilder()
|
||||||
|
.forJob(existingTrigger.getJobKey())
|
||||||
|
.build();
|
||||||
|
|
||||||
TriggerKey triggerKey = TriggerKey.triggerKey(triggerCommandDTO.getTriggerName());
|
|
||||||
scheduler.rescheduleJob(triggerKey, newSimpleTrigger);
|
scheduler.rescheduleJob(triggerKey, newSimpleTrigger);
|
||||||
|
|
||||||
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
|
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package it.fabioformosa.quartzmanager.api.services;
|
package it.fabioformosa.quartzmanager.api.services;
|
||||||
|
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.TriggerDTO;
|
||||||
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
|
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.exceptions.TriggerNotFoundException;
|
||||||
import org.quartz.Scheduler;
|
import org.quartz.Scheduler;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
|
import org.quartz.Trigger;
|
||||||
import org.quartz.TriggerKey;
|
import org.quartz.TriggerKey;
|
||||||
import org.quartz.impl.matchers.GroupMatcher;
|
import org.quartz.impl.matchers.GroupMatcher;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@@ -30,4 +33,36 @@ public class TriggerService {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TriggerDTO getTrigger(String group, String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
|
||||||
|
Trigger trigger = scheduler.getTrigger(triggerKey);
|
||||||
|
if (trigger == null)
|
||||||
|
throw new TriggerNotFoundException(group, name);
|
||||||
|
TriggerDTO triggerDTO = conversionService.convert(trigger, TriggerDTO.class);
|
||||||
|
triggerDTO.setState(scheduler.getTriggerState(triggerKey).name());
|
||||||
|
return triggerDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pauseTrigger(String group, String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
TriggerKey triggerKey = requireTrigger(group, name);
|
||||||
|
scheduler.pauseTrigger(triggerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resumeTrigger(String group, String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
TriggerKey triggerKey = requireTrigger(group, name);
|
||||||
|
scheduler.resumeTrigger(triggerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unscheduleTrigger(String group, String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
TriggerKey triggerKey = requireTrigger(group, name);
|
||||||
|
scheduler.unscheduleJob(triggerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TriggerKey requireTrigger(String group, String name) throws SchedulerException, TriggerNotFoundException {
|
||||||
|
TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
|
||||||
|
if (!scheduler.checkExists(triggerKey))
|
||||||
|
throw new TriggerNotFoundException(group, name);
|
||||||
|
return triggerKey;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package it.fabioformosa.quartzmanager.api.controllers;
|
|||||||
|
|
||||||
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
|
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
|
||||||
import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils;
|
import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.JobKeyDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.ScheduledJobDTO;
|
||||||
import it.fabioformosa.quartzmanager.api.jobs.SampleJob;
|
import it.fabioformosa.quartzmanager.api.jobs.SampleJob;
|
||||||
import it.fabioformosa.quartzmanager.api.services.JobService;
|
import it.fabioformosa.quartzmanager.api.services.JobService;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -16,11 +18,12 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
|
||||||
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
|
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
|
||||||
@WebMvcTest(controllers = SimpleTriggerController.class, properties = {
|
@WebMvcTest(controllers = JobController.class, properties = {
|
||||||
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
|
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
|
||||||
})
|
})
|
||||||
class JobControllerTest {
|
class JobControllerTest {
|
||||||
@@ -36,11 +39,44 @@ class JobControllerTest {
|
|||||||
Mockito.when(jobService.getJobClasses()).thenReturn(List.of(SampleJob.class));
|
Mockito.when(jobService.getJobClasses()).thenReturn(List.of(SampleJob.class));
|
||||||
|
|
||||||
List<String> expectedJobs = List.of(SampleJob.class.getName());
|
List<String> expectedJobs = List.of(SampleJob.class.getName());
|
||||||
mockMvc.perform(get(JobController.JOB_CONTROLLER_BASE_URL)
|
mockMvc.perform(get(JobController.JOB_CLASSES_CONTROLLER_BASE_URL)
|
||||||
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
|
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
|
||||||
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedJobs)));
|
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedJobs)));
|
||||||
|
|
||||||
Mockito.verify(jobService, Mockito.times(1)).getJobClasses();
|
Mockito.verify(jobService, Mockito.times(1)).getJobClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenGetScheduledJobsIsCalled_thenScheduledJobsAreReturned() throws Exception {
|
||||||
|
ScheduledJobDTO scheduledJobDTO = ScheduledJobDTO.builder()
|
||||||
|
.jobKeyDTO(JobKeyDTO.builder().name("sampleJob").group("DEFAULT").build())
|
||||||
|
.jobClassName(SampleJob.class.getName())
|
||||||
|
.build();
|
||||||
|
Mockito.when(jobService.fetchScheduledJobs()).thenReturn(List.of(scheduledJobDTO));
|
||||||
|
|
||||||
|
mockMvc.perform(get(JobController.JOB_CONTROLLER_BASE_URL)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
|
||||||
|
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(List.of(scheduledJobDTO))));
|
||||||
|
|
||||||
|
Mockito.verify(jobService).fetchScheduledJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenTriggerJobIsCalled_thenNoContentIsReturned() throws Exception {
|
||||||
|
mockMvc.perform(post(JobController.JOB_CONTROLLER_BASE_URL + "/DEFAULT/sampleJob/trigger")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isNoContent());
|
||||||
|
|
||||||
|
Mockito.verify(jobService).triggerJob("DEFAULT", "sampleJob");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenDeleteJobIsCalled_thenNoContentIsReturned() throws Exception {
|
||||||
|
mockMvc.perform(delete(JobController.JOB_CONTROLLER_BASE_URL + "/DEFAULT/sampleJob")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isNoContent());
|
||||||
|
|
||||||
|
Mockito.verify(jobService).deleteJob("DEFAULT", "sampleJob");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import org.springframework.test.web.servlet.MockMvc;
|
|||||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||||
|
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
|
||||||
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
|
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
|
||||||
@WebMvcTest(controllers = SimpleTriggerController.class, properties = {
|
@WebMvcTest(controllers = SchedulerController.class, properties = {
|
||||||
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
|
"quartz-manager.jobClassPackages=it.fabioformosa.quartzmanager.jobs"
|
||||||
})
|
})
|
||||||
class SchedulerControllerTest {
|
class SchedulerControllerTest {
|
||||||
@@ -47,8 +48,8 @@ class SchedulerControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenAScheduler_whenTheGetPausedIsCalled_then2xxReturned() throws Exception {
|
void givenAScheduler_whenStandbyIsCalled_then2xxReturned() throws Exception {
|
||||||
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/pause")
|
mockMvc.perform(post(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/standby")
|
||||||
.contentType(MediaType.APPLICATION_JSON))
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
||||||
.andExpect(MockMvcResultMatchers.content().string(""));
|
.andExpect(MockMvcResultMatchers.content().string(""));
|
||||||
@@ -57,8 +58,8 @@ class SchedulerControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenAScheduler_whenTheGetResumedIsCalled_then2xxReturned() throws Exception {
|
void givenAScheduler_whenResumeIsCalled_then2xxReturned() throws Exception {
|
||||||
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/resume")
|
mockMvc.perform(post(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/resume")
|
||||||
.contentType(MediaType.APPLICATION_JSON))
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
||||||
.andExpect(MockMvcResultMatchers.content().string(""));
|
.andExpect(MockMvcResultMatchers.content().string(""));
|
||||||
@@ -67,8 +68,8 @@ class SchedulerControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenAScheduler_whenTheGetRunIsCalled_then2xxReturned() throws Exception {
|
void givenAScheduler_whenStartIsCalled_then2xxReturned() throws Exception {
|
||||||
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/run")
|
mockMvc.perform(post(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/start")
|
||||||
.contentType(MediaType.APPLICATION_JSON))
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
||||||
.andExpect(MockMvcResultMatchers.content().string(""));
|
.andExpect(MockMvcResultMatchers.content().string(""));
|
||||||
@@ -77,8 +78,8 @@ class SchedulerControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenAScheduler_whenTheGetStoppedIsCalled_then2xxReturned() throws Exception {
|
void givenAScheduler_whenShutdownIsCalled_then2xxReturned() throws Exception {
|
||||||
mockMvc.perform(get(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/stop")
|
mockMvc.perform(post(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL + "/shutdown")
|
||||||
.contentType(MediaType.APPLICATION_JSON))
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
.andExpect(MockMvcResultMatchers.status().isNoContent())
|
||||||
.andExpect(MockMvcResultMatchers.content().string(""));
|
.andExpect(MockMvcResultMatchers.content().string(""));
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ class SimpleTriggerControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
void whenGetIsCalled_thenASimpleTriggerIsReturned() throws Exception {
|
void whenGetIsCalled_thenASimpleTriggerIsReturned() throws Exception {
|
||||||
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger");
|
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger");
|
||||||
Mockito.when(simpleTriggerService.getSimpleTriggerByName("mytrigger")).thenReturn(expectedSimpleTriggerDTO);
|
Mockito.when(simpleTriggerService.getSimpleTrigger("DEFAULT", "mytrigger")).thenReturn(expectedSimpleTriggerDTO);
|
||||||
|
|
||||||
mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
|
mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/mytrigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
|
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
|
||||||
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO)));
|
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO)));
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ class SimpleTriggerControllerTest {
|
|||||||
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO);
|
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO);
|
||||||
Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO);
|
Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO);
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
|
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/mytrigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(TestUtils.toJson(simpleTriggerInputDTO))
|
.content(TestUtils.toJson(simpleTriggerInputDTO))
|
||||||
)
|
)
|
||||||
@@ -90,11 +90,12 @@ class SimpleTriggerControllerTest {
|
|||||||
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO);
|
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO);
|
||||||
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
||||||
.triggerName("mytrigger")
|
.triggerName("mytrigger")
|
||||||
|
.triggerGroup("DEFAULT")
|
||||||
.simpleTriggerInputDTO(simpleTriggerInputDTO)
|
.simpleTriggerInputDTO(simpleTriggerInputDTO)
|
||||||
.build();
|
.build();
|
||||||
Mockito.when(simpleTriggerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO)).thenReturn(expectedSimpleTriggerDTO);
|
Mockito.when(simpleTriggerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO)).thenReturn(expectedSimpleTriggerDTO);
|
||||||
|
|
||||||
mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
|
mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/mytrigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(TestUtils.toJson(simpleTriggerInputDTO)))
|
.content(TestUtils.toJson(simpleTriggerInputDTO)))
|
||||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ class SimpleTriggerControllerValidationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenANotExistingTrigger_whenGetIsCalled_then404IsReturned() throws Exception {
|
void givenANotExistingTrigger_whenGetIsCalled_then404IsReturned() throws Exception {
|
||||||
Mockito.when(simpleTriggerService.getSimpleTriggerByName("not_existing_trigger_name")).thenThrow(new TriggerNotFoundException("not_existing_trigger_name"));
|
Mockito.when(simpleTriggerService.getSimpleTrigger("DEFAULT", "not_existing_trigger_name")).thenThrow(new TriggerNotFoundException("DEFAULT", "not_existing_trigger_name"));
|
||||||
|
|
||||||
mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/not_existing_trigger_name")
|
mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/not_existing_trigger_name")
|
||||||
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isNotFound());
|
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isNotFound());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class SimpleTriggerControllerValidationTest {
|
|||||||
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("my-minimal-trigger");
|
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("my-minimal-trigger");
|
||||||
Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO);
|
Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO);
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/my-minimal-trigger")
|
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/my-minimal-trigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(TestUtils.toJson(simpleTriggerInputDTO))
|
.content(TestUtils.toJson(simpleTriggerInputDTO))
|
||||||
)
|
)
|
||||||
@@ -83,7 +83,7 @@ class SimpleTriggerControllerValidationTest {
|
|||||||
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("my-puntual-trigger");
|
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("my-puntual-trigger");
|
||||||
Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO);
|
Mockito.when(simpleTriggerService.scheduleSimpleTrigger(any())).thenReturn(expectedSimpleTriggerDTO);
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/my-puntual-trigger")
|
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/my-puntual-trigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(TestUtils.toJson(simpleTriggerInputDTO))
|
.content(TestUtils.toJson(simpleTriggerInputDTO))
|
||||||
)
|
)
|
||||||
@@ -95,7 +95,7 @@ class SimpleTriggerControllerValidationTest {
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class)
|
@ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class)
|
||||||
void givenAnInvalidSimpleTriggerCommandDTO_whenPostedANewTrigger_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerComandDTO) throws Exception {
|
void givenAnInvalidSimpleTriggerCommandDTO_whenPostedANewTrigger_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerComandDTO) throws Exception {
|
||||||
mockMvc.perform(post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
|
mockMvc.perform(post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/mytrigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(TestUtils.toJson(invalidSimpleTriggerComandDTO)))
|
.content(TestUtils.toJson(invalidSimpleTriggerComandDTO)))
|
||||||
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
|
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
|
||||||
@@ -104,7 +104,7 @@ class SimpleTriggerControllerValidationTest {
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class)
|
@ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class)
|
||||||
void givenAnInvalidSimpleTriggerCommandDTO_whenATriggerIsRescheduled_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerCommandTO) throws Exception {
|
void givenAnInvalidSimpleTriggerCommandDTO_whenATriggerIsRescheduled_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerCommandTO) throws Exception {
|
||||||
mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
|
mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/mytrigger")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(TestUtils.toJson(invalidSimpleTriggerCommandTO)))
|
.content(TestUtils.toJson(invalidSimpleTriggerCommandTO)))
|
||||||
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
|
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
package it.fabioformosa.quartzmanager.api.controllers;
|
package it.fabioformosa.quartzmanager.api.controllers;
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
|
import it.fabioformosa.quartzmanager.api.QuartManagerApplicationTests;
|
||||||
|
import it.fabioformosa.quartzmanager.api.controllers.utils.TestUtils;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.TriggerDTO;
|
||||||
|
import it.fabioformosa.quartzmanager.api.dto.TriggerKeyDTO;
|
||||||
import it.fabioformosa.quartzmanager.api.services.TriggerService;
|
import it.fabioformosa.quartzmanager.api.services.TriggerService;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
|
||||||
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
|
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
|
||||||
@WebMvcTest(controllers = TriggerController.class, properties = {
|
@WebMvcTest(controllers = TriggerController.class, properties = {
|
||||||
@@ -27,4 +39,52 @@ class TriggerControllerTest {
|
|||||||
Mockito.reset(triggerService);
|
Mockito.reset(triggerService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenListTriggersIsCalled_thenTriggersAreReturned() throws Exception {
|
||||||
|
List<TriggerKeyDTO> triggerKeys = List.of(TriggerKeyDTO.builder().name("sampleTrigger").group("DEFAULT").build());
|
||||||
|
Mockito.when(triggerService.fetchTriggers()).thenReturn(triggerKeys);
|
||||||
|
|
||||||
|
mockMvc.perform(get(TriggerController.TRIGGER_CONTROLLER_BASE_URL).contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||||
|
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(triggerKeys)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenGetTriggerIsCalled_thenTriggerIsReturned() throws Exception {
|
||||||
|
TriggerDTO triggerDTO = TriggerDTO.builder()
|
||||||
|
.triggerKeyDTO(TriggerKeyDTO.builder().name("sampleTrigger").group("DEFAULT").build())
|
||||||
|
.state("NORMAL")
|
||||||
|
.type("SimpleTrigger")
|
||||||
|
.build();
|
||||||
|
Mockito.when(triggerService.getTrigger("DEFAULT", "sampleTrigger")).thenReturn(triggerDTO);
|
||||||
|
|
||||||
|
mockMvc.perform(get(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/sampleTrigger").contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||||
|
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(triggerDTO)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenPauseTriggerIsCalled_thenNoContentIsReturned() throws Exception {
|
||||||
|
mockMvc.perform(post(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/sampleTrigger/pause").contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isNoContent());
|
||||||
|
|
||||||
|
Mockito.verify(triggerService).pauseTrigger("DEFAULT", "sampleTrigger");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenResumeTriggerIsCalled_thenNoContentIsReturned() throws Exception {
|
||||||
|
mockMvc.perform(post(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/sampleTrigger/resume").contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isNoContent());
|
||||||
|
|
||||||
|
Mockito.verify(triggerService).resumeTrigger("DEFAULT", "sampleTrigger");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenUnscheduleTriggerIsCalled_thenNoContentIsReturned() throws Exception {
|
||||||
|
mockMvc.perform(delete(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "/DEFAULT/sampleTrigger").contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(MockMvcResultMatchers.status().isNoContent());
|
||||||
|
|
||||||
|
Mockito.verify(triggerService).unscheduleTrigger("DEFAULT", "sampleTrigger");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ class SimpleTriggerServiceTest {
|
|||||||
void givenAnExistingTrigger_whenGetSimplerTriggerByNameIsCalled_thenTheDtoIsReturned() throws SchedulerException, TriggerNotFoundException {
|
void givenAnExistingTrigger_whenGetSimplerTriggerByNameIsCalled_thenTheDtoIsReturned() throws SchedulerException, TriggerNotFoundException {
|
||||||
String existing_trigger = "existing_trigger";
|
String existing_trigger = "existing_trigger";
|
||||||
Mockito.when(scheduler.getTrigger(any(TriggerKey.class)))
|
Mockito.when(scheduler.getTrigger(any(TriggerKey.class)))
|
||||||
.thenReturn(TriggerBuilder.newTrigger().withIdentity(existing_trigger).build());
|
.thenReturn(TriggerBuilder.newTrigger().withIdentity(existing_trigger).withSchedule(SimpleScheduleBuilder.simpleSchedule()).build());
|
||||||
|
Mockito.when(scheduler.getTriggerState(any(TriggerKey.class))).thenReturn(Trigger.TriggerState.NORMAL);
|
||||||
Mockito.when(conversionService.convert(any(SimpleTrigger.class), eq(SimpleTriggerDTO.class)))
|
Mockito.when(conversionService.convert(any(SimpleTrigger.class), eq(SimpleTriggerDTO.class)))
|
||||||
.thenReturn(SimpleTriggerDTO.builder()
|
.thenReturn(SimpleTriggerDTO.builder()
|
||||||
.triggerKeyDTO(TriggerKeyDTO.builder().name(existing_trigger).build())
|
.triggerKeyDTO(TriggerKeyDTO.builder().name(existing_trigger).build())
|
||||||
@@ -81,10 +82,14 @@ class SimpleTriggerServiceTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
Mockito.when(scheduler.scheduleJob(any(), any())).thenReturn(new Date());
|
Mockito.when(scheduler.scheduleJob(any(), any())).thenReturn(new Date());
|
||||||
|
Mockito.when(scheduler.checkExists(any(TriggerKey.class))).thenReturn(false);
|
||||||
|
Mockito.when(conversionService.convert(any(SimpleTriggerCommandDTO.class), eq(SimpleTrigger.class)))
|
||||||
|
.thenReturn(TriggerBuilder.newTrigger().withIdentity(simpleTriggerName, "DEFAULT").withSchedule(SimpleScheduleBuilder.simpleSchedule()).build());
|
||||||
Mockito.when(conversionService.convert(any(), eq(SimpleTriggerDTO.class))).thenReturn(expectedTriggerDTO);
|
Mockito.when(conversionService.convert(any(), eq(SimpleTriggerDTO.class))).thenReturn(expectedTriggerDTO);
|
||||||
|
|
||||||
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
||||||
.triggerName(simpleTriggerName)
|
.triggerName(simpleTriggerName)
|
||||||
|
.triggerGroup("DEFAULT")
|
||||||
.simpleTriggerInputDTO(triggerInputDTO)
|
.simpleTriggerInputDTO(triggerInputDTO)
|
||||||
.build();
|
.build();
|
||||||
SimpleTriggerDTO simpleTrigger = simpleSchedulerService.scheduleSimpleTrigger(simpleTriggerCommandDTO);
|
SimpleTriggerDTO simpleTrigger = simpleSchedulerService.scheduleSimpleTrigger(simpleTriggerCommandDTO);
|
||||||
@@ -93,7 +98,7 @@ class SimpleTriggerServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenASimpleTriggerCommandDTO_whenASimpleTriggerIsRecheduled_thenATriggerDTOIsReturned() throws SchedulerException, ClassNotFoundException {
|
void givenASimpleTriggerCommandDTO_whenASimpleTriggerIsRecheduled_thenATriggerDTOIsReturned() throws SchedulerException, ClassNotFoundException, TriggerNotFoundException {
|
||||||
SimpleTriggerInputDTO triggerInputDTO = SimpleTriggerInputDTO.builder()
|
SimpleTriggerInputDTO triggerInputDTO = SimpleTriggerInputDTO.builder()
|
||||||
.jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob")
|
.jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob")
|
||||||
.startDate(new Date())
|
.startDate(new Date())
|
||||||
@@ -115,10 +120,15 @@ class SimpleTriggerServiceTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
Mockito.when(scheduler.rescheduleJob(any(), any())).thenReturn(new Date());
|
Mockito.when(scheduler.rescheduleJob(any(), any())).thenReturn(new Date());
|
||||||
|
Mockito.when(scheduler.getTrigger(any(TriggerKey.class)))
|
||||||
|
.thenReturn(TriggerBuilder.newTrigger().withIdentity(simpleTriggerName, "DEFAULT").forJob(JobKey.jobKey("MyJob", "DEFAULT")).withSchedule(SimpleScheduleBuilder.simpleSchedule()).build());
|
||||||
|
Mockito.when(conversionService.convert(any(SimpleTriggerCommandDTO.class), eq(SimpleTrigger.class)))
|
||||||
|
.thenReturn(TriggerBuilder.newTrigger().withIdentity(simpleTriggerName, "DEFAULT").withSchedule(SimpleScheduleBuilder.simpleSchedule()).build());
|
||||||
Mockito.when(conversionService.convert(any(), eq(SimpleTriggerDTO.class))).thenReturn(expectedTriggerDTO);
|
Mockito.when(conversionService.convert(any(), eq(SimpleTriggerDTO.class))).thenReturn(expectedTriggerDTO);
|
||||||
|
|
||||||
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
|
||||||
.triggerName(simpleTriggerName)
|
.triggerName(simpleTriggerName)
|
||||||
|
.triggerGroup("DEFAULT")
|
||||||
.simpleTriggerInputDTO(triggerInputDTO)
|
.simpleTriggerInputDTO(triggerInputDTO)
|
||||||
.build();
|
.build();
|
||||||
SimpleTriggerDTO simpleTrigger = simpleSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO);
|
SimpleTriggerDTO simpleTrigger = simpleSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO);
|
||||||
|
|||||||
Reference in New Issue
Block a user