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
8 changes: 8 additions & 0 deletions public/locales/de-DE/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,14 @@
"called-definitions": {
"called-process-definition": "Aufgerufene Prozessdefinition"
},
"external-tasks": {
"topic": "Thema",
"worker": "Worker",
"lock-expiration": "Sperrablauf",
"retries": "Retries",
"error-message": "Fehlermeldung",
"empty": "Keine externen Aufgaben gefunden."
},
"jobs": {
"overriding-job-priority": "Überschriebene Job-Priorität",
"suspend": "Anhalten",
Expand Down
8 changes: 8 additions & 0 deletions public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,14 @@
"called-definitions": {
"called-process-definition": "Called Process Definition"
},
"external-tasks": {
"topic": "Topic",
"worker": "Worker",
"lock-expiration": "Lock Expiration",
"retries": "Retries",
"error-message": "Error Message",
"empty": "No external tasks found."
},
"jobs": {
"overriding-job-priority": "Overriding Job Priority",
"suspend": "Suspend",
Expand Down
2 changes: 2 additions & 0 deletions src/api/engine_rest.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import tenant from "./resources/tenant.js";
import process_definition from "./resources/process_definition.js";
import process_instance from "./resources/process_instance.js";
import deployment from "./resources/deployment.js";
import external_task from "./resources/external_task.js";
import history from "./resources/history.js";
import job_definition from "./resources/job_definition.js";
import migration from "./resources/migration.js";
Expand All @@ -26,6 +27,7 @@ const engine_rest = {
decision,
deployment,
engine,
external_task,
filter,
group,
history,
Expand Down
14 changes: 14 additions & 0 deletions src/api/resources/external_task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { GET } from "../helper.jsx";

const get_external_tasks_by_process_instance = (state, process_instance_id) =>
GET(
`/external-task?processInstanceId=${process_instance_id}`,
state,
state.api.external_task.by_process_instance,
);

const external_task = {
by_process_instance: get_external_tasks_by_process_instance,
};

export default external_task;
25 changes: 25 additions & 0 deletions src/api/resources/external_task.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, it, vi, beforeEach } from "vitest";

vi.mock("../helper.jsx", () => ({
GET: vi.fn(),
}));

import { GET } from "../helper.jsx";
import { create_mock_state, expect_api_call } from "../../test/helpers.js";
import external_task from "./external_task.js";

describe("api/resources/external_task", () => {
let state;
beforeEach(() => {
state = create_mock_state();
});

it("by_process_instance() GETs external tasks for a process instance", () => {
external_task.by_process_instance(state, "inst-1");
expect_api_call(GET, {
url: "/external-task?processInstanceId=inst-1",
state,
signal: state.api.external_task.by_process_instance,
});
});
});
105 changes: 80 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 @@ -1464,13 +1459,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 Expand Up @@ -1573,8 +1561,75 @@ const UUIDLink = ({ uuid = "?", path }) => (

// TODO: create Jobs example for old Camunda apps
const InstanceJobsPlaceholder = () => <p>Jobs</p>;
// TODO: create External Apps example for old Camunda apps
const InstanceExternalTasksPlaceholder = () => <p>External Tasks</p>;
const InstanceExternalTasks = () => {
const state = useContext(AppState),
{ selection_id, query } = useRoute(),
history_mode = query.history === "true",
[t] = useTranslation();

useEffect(() => {
if (!history_mode) {
void engine_rest.external_task.by_process_instance(state, selection_id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selection_id, history_mode]);

if (history_mode) {
return <p class="info-box">{t("processes.history-mode-na")}</p>;
}

/** @namespace state.api.external_task.by_process_instance.value.data **/
return (
<RequestState
signal={state.api.external_task.by_process_instance}
on_success={() => {
const rows =
state.api.external_task.by_process_instance.value?.data ?? [];
if (rows.length === 0) {
return <p class="info-box">{t("processes.external-tasks.empty")}</p>;
}
return (
<table>
<thead>
<tr>
<th>{t("common.id")}</th>
<th>{t("common.activity")}</th>
<th>{t("processes.external-tasks.topic")}</th>
<th>{t("processes.external-tasks.worker")}</th>
<th>{t("processes.external-tasks.lock-expiration")}</th>
<th>{t("processes.external-tasks.retries")}</th>
<th>{t("tasks.task-list.table-headings.priority")}</th>
<th>{t("processes.external-tasks.error-message")}</th>
</tr>
</thead>
<tbody>
{rows.map((task) => (
<tr key={task.id}>
<td class="font-mono">{task.id?.substring(0, 8)}</td>
<td>{task.activityId ?? "—"}</td>
<td>{task.topicName ?? "—"}</td>
<td>{task.workerId ?? "—"}</td>
<td>
{task.lockExpirationTime ? (
<time datetime={task.lockExpirationTime}>
{task.lockExpirationTime}
</time>
) : (
"—"
)}
</td>
<td>{task.retries ?? "—"}</td>
<td>{task.priority ?? "—"}</td>
<td>{task.errorMessage ?? "—"}</td>
</tr>
))}
</tbody>
</table>
);
}}
/>
);
};

const process_instance_tabs = [
{
Expand Down Expand Up @@ -1611,7 +1666,7 @@ const process_instance_tabs = [
nameKey: "processes.tabs.external-tasks",
id: "external_tasks",
pos: 5,
Component: InstanceExternalTasksPlaceholder,
Component: InstanceExternalTasks,
},
];

Expand Down
41 changes: 41 additions & 0 deletions src/pages/Processes.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,47 @@ describe("ProcessesPage — instance details", () => {
expect(getByText("Approve (historic)")).toBeTruthy();
});

it("external-tasks sub-panel fetches and renders external tasks", () => {
mockParams = {
definition_id: "proc:1",
panel: "instances",
selection_id: "inst-9999",
sub_panel: "external_tasks",
};
signal_response(state.api.external_task.by_process_instance, [
{
id: "external-task-1",
activityId: "serviceTask",
topicName: "billing-topic",
workerId: "worker-1",
lockExpirationTime: "2024-01-01T00:00:00Z",
retries: 3,
priority: 50,
errorMessage: "boom",
},
]);
const { getByText } = renderPage(state);
expect(engine_rest.external_task.by_process_instance).toHaveBeenCalled();
expect(getByText("external")).toBeTruthy();
expect(getByText("serviceTask")).toBeTruthy();
expect(getByText("billing-topic")).toBeTruthy();
expect(getByText("worker-1")).toBeTruthy();
expect(getByText("boom")).toBeTruthy();
});

it("external-tasks sub-panel does not fetch live external tasks in history mode", () => {
mockParams = {
definition_id: "proc:1",
panel: "instances",
selection_id: "inst-9999",
sub_panel: "external_tasks",
};
mockQuery = { history: "true" };
const { getByText } = renderPage(state);
expect(engine_rest.external_task.by_process_instance).not.toHaveBeenCalled();
expect(getByText("processes.history-mode-na")).toBeTruthy();
});

it("called-instances sub-panel uses the historic endpoint in history mode", () => {
mockParams = {
definition_id: "proc:1",
Expand Down
3 changes: 3 additions & 0 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ const createAppState = () => {
delete: signal(null),
saved_filters: signal(null),
},
external_task: {
by_process_instance: signal(null),
},
decision: {
definitions: signal(null),
definition: 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 @@ -53,6 +53,7 @@ describe("state", () => {
const { api } = createAppState();
expect(api.process.definition.list.value).toBeNull();
expect(api.process.instance.one.value).toBeNull();
expect(api.external_task.by_process_instance.value).toBeNull();
expect(api.task.comment.list.value).toBeNull();
expect(api.authorization.all.value).toBeNull();
expect(api.batch.list.value).toBeNull();
Expand Down