diff --git a/public/locales/de-DE/translation.json b/public/locales/de-DE/translation.json index 5f7d6a9..cb77887 100644 --- a/public/locales/de-DE/translation.json +++ b/public/locales/de-DE/translation.json @@ -345,6 +345,9 @@ "jobs": { "overriding-job-priority": "Überschriebene Job-Priorität", "suspend": "Anhalten", + "activate": "Aktivieren", + "include-jobs": "Bestehende Jobs einbeziehen", + "suspension-updated": "Status der Job-Definition aktualisiert.", "change-priority": "Job-Priorität ändern" }, "sort": { diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index ac20fbe..d521846 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -345,6 +345,9 @@ "jobs": { "overriding-job-priority": "Overriding Job Priority", "suspend": "Suspend", + "activate": "Activate", + "include-jobs": "Include existing jobs", + "suspension-updated": "Job definition state updated.", "change-priority": "Change Overriding Job Priority" }, "sort": { diff --git a/src/api/resources/job_definition.js b/src/api/resources/job_definition.js index 52c2e87..7822943 100644 --- a/src/api/resources/job_definition.js +++ b/src/api/resources/job_definition.js @@ -1,12 +1,21 @@ -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) +const set_job_definition_suspended = (state, id, suspended, includeJobs = false) => + PUT( + `/job-definition/${id}/suspended`, + { suspended, includeJobs }, + state, + state.api.job_definition.update, + ) + const job_definition = { all: { by_process_definition: get_job_definitions - } + }, + set_suspended: set_job_definition_suspended, } -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..32c1d3c 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,14 @@ describe("api/resources/job_definition", () => { signal: state.api.job_definition.all.by_process_definition, }); }); + + it("set_suspended() PUTs the suspended state for a job definition", () => { + job_definition.set_suspended(state, "job-def-1", true, true); + expect_api_call(PUT, { + url: "/job-definition/job-def-1/suspended", + body: { suspended: true, includeJobs: true }, + state, + signal: state.api.job_definition.update, + }); + }); }); diff --git a/src/css/components.css b/src/css/components.css index 5bed3e4..31de438 100644 --- a/src/css/components.css +++ b/src/css/components.css @@ -869,6 +869,25 @@ div.accordion { color: var(--text-2); } +/* process job definitions */ + +.job-definition-actions { + display: flex; + align-items: center; + gap: var(--spacing-1); + margin-bottom: var(--spacing-1); +} + +.job-definition-actions label { + display: flex; + align-items: center; + gap: var(--spacing-half); +} + +.job-definition-actions input[type="checkbox"] { + width: auto; +} + /* task detail cards */ .task-cards { diff --git a/src/pages/Processes.jsx b/src/pages/Processes.jsx index d6ac79e..c71fe8e 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 ( @@ -1043,20 +1042,16 @@ const InstanceVariables = () => { ? !history_mode ? Object.entries( state.api.process.instance.variables.value.data, - ).map( - // eslint-disable-next-line react/jsx-key - ([name, { type, value }]) => ( - - {name} - {type} - {value} - - ), - ) + ).map(([name, { type, value }]) => ( + + {name} + {type} + {value} + + )) : state.api.process.instance.variables.value.data.map( - // eslint-disable-next-line react/jsx-key ({ name, type, value }) => ( - + {name} {type} {value} @@ -1407,7 +1402,9 @@ const JobDefinitions = () => { const state = useContext(AppState), { definition_id, query } = useRoute(), history_mode = query?.history === "true", - [t] = useTranslation(); + [t] = useTranslation(), + include_jobs = useSignal(false), + update = state.api.job_definition.update; useEffect(() => { void engine_rest.job_definition.all.by_process_definition( @@ -1417,6 +1414,19 @@ const JobDefinitions = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [definition_id]); + const set_suspended = async (id, suspended) => { + await engine_rest.job_definition.set_suspended( + state, + id, + suspended, + include_jobs.value, + ); + void engine_rest.job_definition.all.by_process_definition( + state, + definition_id, + ); + }; + /** @namespace state.api.job_definition.all.by_process_definition.value.data **/ /** @namespace definition.jobType **/ /** @namespace definition.jobConfiguration **/ @@ -1426,6 +1436,23 @@ const JobDefinitions = () => { {history_mode && ( {t("processes.history-mode-na")} )} +
+ +
+ null} + on_success={() => ( +

{t("processes.jobs.suspension-updated")}

+ )} + /> @@ -1452,8 +1479,21 @@ const JobDefinitions = () => { ), @@ -1464,13 +1504,6 @@ const JobDefinitions = () => { ); }; -const BackToListBtn = ({ url, title, className }) => ( - - - - -); - const DefinitionsManage = () => { const state = useContext(AppState), { route } = useLocation(), diff --git a/src/pages/Processes.test.jsx b/src/pages/Processes.test.jsx index 60c4b48..b38368e 100644 --- a/src/pages/Processes.test.jsx +++ b/src/pages/Processes.test.jsx @@ -356,6 +356,59 @@ describe("ProcessesPage — definition tabs", () => { expect(getByText("timer")).toBeTruthy(); expect(getByText("R/PT5M")).toBeTruthy(); }); + + it("jobs tab suspends an active job definition", async () => { + mockParams = { definition_id: "proc:1", panel: "jobs" }; + signal_response(state.api.job_definition.all.by_process_definition, [ + { + id: "jd1", + suspended: false, + jobType: "timer", + jobConfiguration: "R/PT5M", + }, + ]); + engine_rest.job_definition.set_suspended.mockResolvedValue(undefined); + const { container, getByText } = renderPage(state); + fireEvent.click( + container.querySelector( + '.job-definition-actions input[type="checkbox"]', + ), + ); + fireEvent.click(getByText("processes.jobs.suspend")); + await Promise.resolve(); + await Promise.resolve(); + expect(engine_rest.job_definition.set_suspended).toHaveBeenCalled(); + expect(engine_rest.job_definition.set_suspended.mock.lastCall).toEqual([ + state, + "jd1", + true, + true, + ]); + }); + + it("jobs tab activates a suspended job definition", async () => { + mockParams = { definition_id: "proc:1", panel: "jobs" }; + signal_response(state.api.job_definition.all.by_process_definition, [ + { + id: "jd1", + suspended: true, + jobType: "timer", + jobConfiguration: "R/PT5M", + }, + ]); + engine_rest.job_definition.set_suspended.mockResolvedValue(undefined); + const { getByText } = renderPage(state); + fireEvent.click(getByText("processes.jobs.activate")); + await Promise.resolve(); + await Promise.resolve(); + expect(engine_rest.job_definition.set_suspended).toHaveBeenCalled(); + expect(engine_rest.job_definition.set_suspended.mock.lastCall).toEqual([ + state, + "jd1", + false, + false, + ]); + }); }); describe("ProcessesPage — instance details", () => { diff --git a/src/state.js b/src/state.js index f63ad2e..4a90f14 100644 --- a/src/state.js +++ b/src/state.js @@ -183,6 +183,7 @@ const createAppState = () => { all: { by_process_definition: signal(null), }, + update: signal(null), }, }; diff --git a/src/state.test.js b/src/state.test.js index c6b9968..86a2ac1 100644 --- a/src/state.test.js +++ b/src/state.test.js @@ -57,6 +57,7 @@ describe("state", () => { expect(api.authorization.all.value).toBeNull(); expect(api.batch.list.value).toBeNull(); expect(api.batch.one.value).toBeNull(); + expect(api.job_definition.update.value).toBeNull(); }); }); });
{definition.jobConfiguration} {definition.overridingJobPriority ?? "-"} - - + {definition.suspended ? ( + + ) : ( + + )}