diff --git a/public/locales/de-DE/translation.json b/public/locales/de-DE/translation.json index 5f7d6a9..3ecd280 100644 --- a/public/locales/de-DE/translation.json +++ b/public/locales/de-DE/translation.json @@ -345,7 +345,11 @@ "jobs": { "overriding-job-priority": "Überschriebene Job-Priorität", "suspend": "Anhalten", - "change-priority": "Job-Priorität ändern" + "change-priority": "Job-Priorität ändern", + "priority": "Priorität", + "include-jobs": "Auf bestehende Jobs anwenden", + "reset-priority": "Priorität zurücksetzen", + "priority-success": "Job-Definition-Priorität aktualisiert." }, "sort": { "name": "Name", diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index ac20fbe..314e093 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -345,7 +345,11 @@ "jobs": { "overriding-job-priority": "Overriding Job Priority", "suspend": "Suspend", - "change-priority": "Change Overriding Job Priority" + "change-priority": "Change Overriding Job Priority", + "priority": "Priority", + "include-jobs": "Apply to existing jobs", + "reset-priority": "Reset Priority", + "priority-success": "Job definition priority updated." }, "sort": { "name": "Name", diff --git a/src/api/resources/job_definition.js b/src/api/resources/job_definition.js index 52c2e87..0520c51 100644 --- a/src/api/resources/job_definition.js +++ b/src/api/resources/job_definition.js @@ -1,12 +1,25 @@ -import { GET } from '../helper.jsx' +import { GET, PUT } from "../helper.jsx"; const get_job_definitions = (state, definition_id) => - GET(`/job-definition?processDefinitionId=${definition_id}`, state, state.api.job_definition.all.by_process_definition) + GET( + `/job-definition?processDefinitionId=${definition_id}`, + state, + state.api.job_definition.all.by_process_definition, + ); + +const set_priority = (state, id, priority, include_jobs = false) => + PUT( + `/job-definition/${id}/jobPriority`, + { priority, includeJobs: priority !== null && include_jobs }, + state, + state.api.job_definition.priority, + ); const job_definition = { all: { - by_process_definition: get_job_definitions - } -} + by_process_definition: get_job_definitions, + }, + set_priority, +}; -export default job_definition \ No newline at end of file +export default job_definition; diff --git a/src/api/resources/job_definition.test.js b/src/api/resources/job_definition.test.js index dde29f3..b1d66b5 100644 --- a/src/api/resources/job_definition.test.js +++ b/src/api/resources/job_definition.test.js @@ -2,9 +2,10 @@ import { describe, it, vi, beforeEach } from "vitest"; vi.mock("../helper.jsx", () => ({ GET: vi.fn(), + PUT: vi.fn(), })); -import { GET } from "../helper.jsx"; +import { GET, PUT } from "../helper.jsx"; import { create_mock_state, expect_api_call } from "../../test/helpers.js"; import job_definition from "./job_definition.js"; @@ -22,4 +23,24 @@ describe("api/resources/job_definition", () => { signal: state.api.job_definition.all.by_process_definition, }); }); + + it("set_priority() PUTs overriding priority to the job definition", () => { + job_definition.set_priority(state, "job-def-1", 42, true); + expect_api_call(PUT, { + url: "/job-definition/job-def-1/jobPriority", + body: { priority: 42, includeJobs: true }, + state, + signal: state.api.job_definition.priority, + }); + }); + + it("set_priority() resets priority without propagating to existing jobs", () => { + job_definition.set_priority(state, "job-def-1", null, true); + expect_api_call(PUT, { + url: "/job-definition/job-def-1/jobPriority", + body: { priority: null, includeJobs: false }, + state, + signal: state.api.job_definition.priority, + }); + }); }); diff --git a/src/pages/Processes.jsx b/src/pages/Processes.jsx index d6ac79e..538b9a0 100644 --- a/src/pages/Processes.jsx +++ b/src/pages/Processes.jsx @@ -686,7 +686,7 @@ const DefinitionsEmpty = () => { {t("processes.empty.how-to")} @@ -928,8 +928,7 @@ const InstanceDetails = () => { params: { selection_id, definition_id, panel }, query, } = useRoute(), - history_mode = query.history === "true", - [t] = useTranslation(); + history_mode = query.history === "true"; if (selection_id) { if ( @@ -1044,9 +1043,8 @@ const InstanceVariables = () => { ? Object.entries( state.api.process.instance.variables.value.data, ).map( - // eslint-disable-next-line react/jsx-key ([name, { type, value }]) => ( - + {name} {type} {value} @@ -1054,9 +1052,8 @@ const InstanceVariables = () => { ), ) : state.api.process.instance.variables.value.data.map( - // eslint-disable-next-line react/jsx-key ({ name, type, value }) => ( - + {name} {type} {value} @@ -1426,6 +1423,14 @@ const JobDefinitions = () => { {history_mode && ( {t("processes.history-mode-na")} )} + {state.api.job_definition.priority.value ? ( + ( +

{t("processes.jobs.priority-success")}

+ )} + /> + ) : null} @@ -1440,22 +1445,7 @@ const JobDefinitions = () => { {state.api.job_definition.all.by_process_definition.value?.data?.map( (definition) => ( - - - - {/**/} - - - - - + ), )} @@ -1464,12 +1454,80 @@ const JobDefinitions = () => { ); }; -const BackToListBtn = ({ url, title, className }) => ( - - - - -); +const JobDefinitionRow = ({ definition }) => { + const state = useContext(AppState), + { definition_id } = useRoute(), + [t] = useTranslation(), + priority = useSignal(definition.overridingJobPriority ?? ""), + include_jobs = useSignal(false), + input_id = `job-priority-${definition.id}`; + + const refresh = () => + engine_rest.job_definition.all.by_process_definition( + state, + definition_id, + ), + on_submit = (e) => { + e.preventDefault(); + const raw = String(priority.value).trim(); + if (raw === "") return; + const parsed = Number(raw); + if (!Number.isFinite(parsed)) return; + void engine_rest.job_definition.set_priority( + state, + definition.id, + parsed, + include_jobs.value, + ).then(refresh); + }, + on_reset = () => + engine_rest.job_definition.set_priority( + state, + definition.id, + null, + false, + ).then(refresh); + + return ( + + + + + + + + + ); +}; const DefinitionsManage = () => { const state = useContext(AppState), diff --git a/src/pages/Processes.test.jsx b/src/pages/Processes.test.jsx index 60c4b48..7c475c5 100644 --- a/src/pages/Processes.test.jsx +++ b/src/pages/Processes.test.jsx @@ -356,6 +356,70 @@ describe("ProcessesPage — definition tabs", () => { expect(getByText("timer")).toBeTruthy(); expect(getByText("R/PT5M")).toBeTruthy(); }); + + it("sets an overriding job definition priority", async () => { + mockParams = { definition_id: "proc:1", panel: "jobs" }; + engine_rest.job_definition.set_priority.mockResolvedValue(undefined); + signal_response(state.api.job_definition.all.by_process_definition, [ + { + id: "jd1", + suspended: false, + activityId: "timer_start", + jobType: "timer", + jobConfiguration: "R/PT5M", + overridingJobPriority: null, + }, + ]); + const { container, getByLabelText, getByText } = renderPage(state); + + fireEvent.input(getByLabelText("processes.jobs.priority"), { + target: { value: "42" }, + }); + fireEvent.click(getByLabelText("processes.jobs.include-jobs")); + fireEvent.submit(container.querySelector("form")); + await Promise.resolve(); + await Promise.resolve(); + + expect(engine_rest.job_definition.set_priority).toHaveBeenCalled(); + expect(engine_rest.job_definition.set_priority.mock.lastCall[0]).toBe( + state, + ); + expect(engine_rest.job_definition.set_priority.mock.lastCall[1]).toBe( + "jd1", + ); + expect(engine_rest.job_definition.set_priority.mock.lastCall[2]).toBe(42); + expect(engine_rest.job_definition.set_priority.mock.lastCall[3]).toBe(true); + expect(getByText("timer_start")).toBeTruthy(); + }); + + it("resets an overriding job definition priority", async () => { + mockParams = { definition_id: "proc:1", panel: "jobs" }; + engine_rest.job_definition.set_priority.mockResolvedValue(undefined); + signal_response(state.api.job_definition.all.by_process_definition, [ + { + id: "jd1", + suspended: false, + activityId: "timer_start", + jobType: "timer", + jobConfiguration: "R/PT5M", + overridingJobPriority: 10, + }, + ]); + const { getByText } = renderPage(state); + + fireEvent.click(getByText("processes.jobs.reset-priority")); + await Promise.resolve(); + await Promise.resolve(); + + expect(engine_rest.job_definition.set_priority).toHaveBeenCalled(); + expect(engine_rest.job_definition.set_priority.mock.lastCall[1]).toBe( + "jd1", + ); + expect(engine_rest.job_definition.set_priority.mock.lastCall[2]).toBe(null); + expect(engine_rest.job_definition.set_priority.mock.lastCall[3]).toBe( + false, + ); + }); }); describe("ProcessesPage — instance details", () => { diff --git a/src/state.js b/src/state.js index f63ad2e..e3d1e3c 100644 --- a/src/state.js +++ b/src/state.js @@ -183,6 +183,7 @@ const createAppState = () => { all: { by_process_definition: signal(null), }, + priority: signal(null), }, };
- {definition.suspended - ? t("common.suspended") - : t("common.active")} - ?{definition.calledFromActivityIds.map(a => `${a}, `)}{definition.jobType}{definition.jobConfiguration}{definition.overridingJobPriority ?? "-"} - - -
+ {definition.suspended + ? t("common.suspended") + : t("common.active")} + {definition.activityId ?? "—"}{definition.jobType}{definition.jobConfiguration}{definition.overridingJobPriority ?? "-"} +
+ + (priority.value = e.currentTarget.value)} + required + /> + +
+ + +
+
+