Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/locales/de-DE/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 3 additions & 0 deletions public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
15 changes: 12 additions & 3 deletions src/api/resources/job_definition.js
Original file line number Diff line number Diff line change
@@ -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
export default job_definition
13 changes: 12 additions & 1 deletion src/api/resources/job_definition.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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,
});
});
});
19 changes: 19 additions & 0 deletions src/css/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
83 changes: 58 additions & 25 deletions src/pages/Processes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ const DefinitionsEmpty = () => {
<a
href="https://docs.operaton.org/manual/latest/installation/full/tomcat/manual/"
target="_blank"
rel="noopener"
rel="noreferrer"
>
{t("processes.empty.how-to")}
</a>
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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 }]) => (
<tr>
<td>{name}</td>
<td>{type}</td>
<td>{value}</td>
</tr>
),
)
).map(([name, { type, value }]) => (
<tr key={name}>
<td>{name}</td>
<td>{type}</td>
<td>{value}</td>
</tr>
))
: state.api.process.instance.variables.value.data.map(
// eslint-disable-next-line react/jsx-key
({ name, type, value }) => (
<tr>
<tr key={name}>
<td>{name}</td>
<td>{type}</td>
<td>{value}</td>
Expand Down Expand Up @@ -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(
Expand All @@ -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 **/
Expand All @@ -1426,6 +1436,23 @@ const JobDefinitions = () => {
{history_mode && (
<small class="history-na">{t("processes.history-mode-na")}</small>
)}
<div class="job-definition-actions">
<label>
<input
type="checkbox"
checked={include_jobs.value}
onChange={(e) => (include_jobs.value = e.currentTarget.checked)}
/>
{t("processes.jobs.include-jobs")}
</label>
</div>
<RequestState
signal={update}
on_nothing={() => null}
on_success={() => (
<p class="info-box">{t("processes.jobs.suspension-updated")}</p>
)}
/>
<table>
<thead>
<tr>
Expand All @@ -1452,8 +1479,21 @@ const JobDefinitions = () => {
<td>{definition.jobConfiguration}</td>
<td>{definition.overridingJobPriority ?? "-"}</td>
<td>
<button>{t("processes.jobs.suspend")}</button>
<button>{t("processes.jobs.change-priority")}</button>
{definition.suspended ? (
<button
type="button"
onClick={() => set_suspended(definition.id, false)}
>
{t("processes.jobs.activate")}
</button>
) : (
<button
type="button"
onClick={() => set_suspended(definition.id, true)}
>
{t("processes.jobs.suspend")}
</button>
)}
</td>
</tr>
),
Expand All @@ -1464,13 +1504,6 @@ const JobDefinitions = () => {
);
};

const BackToListBtn = ({ url, title, className }) => (
<a className={`tabs-back ${className || ""}`} href={url} title={title}>
<Icons.arrow_left />
<Icons.list />
</a>
);

const DefinitionsManage = () => {
const state = useContext(AppState),
{ route } = useLocation(),
Expand Down
53 changes: 53 additions & 0 deletions src/pages/Processes.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
1 change: 1 addition & 0 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const createAppState = () => {
all: {
by_process_definition: signal(null),
},
update: signal(null),
},
};

Expand Down
1 change: 1 addition & 0 deletions src/state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});