This is an automated email from the ASF dual-hosted git repository.
fantonangeli pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git
The following commit(s) were added to refs/heads/main by this push:
new e607b8affe1 kie-issues#2656: [SonataFlow] Jobs List Status Continues
to Report "Expires in NN Minutes" After Job Expiration (#2727)
e607b8affe1 is described below
commit e607b8affe10102b18d35d1b95c350adb44fc6af
Author: Fabrizio Antonangeli <[email protected]>
AuthorDate: Tue Nov 12 15:35:17 2024 +0100
kie-issues#2656: [SonataFlow] Jobs List Status Continues to Report "Expires
in NN Minutes" After Job Expiration (#2727)
---
.../components/WorkflowDetails/WorkflowDetails.tsx | 60 ++++--
.../tests/components/WorkflowDetails.test.tsx | 81 +++++++-
packages/sonataflow-dev-app/README.md | 20 ++
.../sonataflow-dev-app/src/MockData/graphql.js | 208 ++++++++++++++++++++-
packages/sonataflow-dev-app/src/MockData/types.js | 4 +
packages/sonataflow-dev-app/src/server.js | 21 ++-
6 files changed, 377 insertions(+), 17 deletions(-)
diff --git
a/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx
b/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx
index 5f2ea170b45..a0fdb9a1fc0 100644
---
a/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx
+++
b/packages/runtime-tools-swf-enveloped-components/src/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useState, useCallback } from "react";
import { Flex, FlexItem } from "@patternfly/react-core/dist/js/layouts/Flex";
import { Grid, GridItem } from "@patternfly/react-core/dist/js/layouts/Grid";
import { Split, SplitItem } from
"@patternfly/react-core/dist/js/layouts/Split";
@@ -48,9 +48,15 @@ import WorkflowVariables from
"../WorkflowVariables/WorkflowVariables";
import WorkflowDetailsMilestonesPanel from
"../WorkflowDetailsMilestonesPanel/WorkflowDetailsMilestonesPanel";
import WorkflowDetailsTimelinePanel from
"../WorkflowDetailsTimelinePanel/WorkflowDetailsTimelinePanel";
import SwfCombinedEditor from "../SwfCombinedEditor/SwfCombinedEditor";
-import { Job, WorkflowInstance, WorkflowInstanceState } from
"@kie-tools/runtime-tools-swf-gateway-api/dist/types";
+import {
+ Job,
+ JobStatus,
+ WorkflowInstance,
+ WorkflowInstanceState,
+} from "@kie-tools/runtime-tools-swf-gateway-api/dist/types";
const SWFCOMBINEDEDITOR_WIDTH = 1000;
+const CHECK_EXPIRED_JOBS_TIMEOUT = 5000;
interface WorkflowDetailsProps {
isEnvelopeConnectedToChannel: boolean;
@@ -78,7 +84,7 @@ const WorkflowDetails: React.FC<WorkflowDetailsProps> = ({
isEnvelopeConnectedTo
try {
const workflowResponse: WorkflowInstance = await
driver.workflowDetailsQuery(workflowDetails.id);
workflowResponse && setData(workflowResponse);
- getAllJobs();
+ loadJobs();
setIsLoading(false);
} catch (errorString) {
setError(errorString);
@@ -86,10 +92,36 @@ const WorkflowDetails: React.FC<WorkflowDetailsProps> = ({
isEnvelopeConnectedTo
}
};
- const getAllJobs = async (): Promise<void> => {
+ const loadJobs = useCallback(async () => {
const jobsResponse: Job[] = await driver.jobsQuery(workflowDetails.id);
jobsResponse && setJobs(jobsResponse);
- };
+ }, [workflowDetails.id, driver]);
+
+ /**
+ * check every N seconds for jobs which are SCHEDULED and epired
+ * @return
+ */
+ const checkExpiredJobs = useCallback(async () => {
+ await new Promise((resolve) => setTimeout(resolve,
CHECK_EXPIRED_JOBS_TIMEOUT));
+ const scheduledJobs = jobs.filter((job) => job.status ===
JobStatus.Scheduled);
+
+ if (!scheduledJobs.length) {
+ return;
+ }
+
+ const expiredJob = scheduledJobs.find((job) => new
Date(job.expirationTime) < new Date());
+
+ if (expiredJob) {
+ loadJobs();
+ return;
+ }
+
+ checkExpiredJobs();
+ }, [loadJobs, jobs]);
+
+ useEffect(() => {
+ jobs.length && checkExpiredJobs();
+ }, [jobs, checkExpiredJobs]);
useEffect(() => {
const getVariableJSON = (): void => {
@@ -101,7 +133,7 @@ const WorkflowDetails: React.FC<WorkflowDetailsProps> = ({
isEnvelopeConnectedTo
if (isEnvelopeConnectedToChannel) {
getVariableJSON();
}
- }, [data]);
+ }, [data, isEnvelopeConnectedToChannel, workflowDetails.id]);
useEffect(() => {
if (variableError && variableError.length > 0) {
@@ -109,12 +141,16 @@ const WorkflowDetails: React.FC<WorkflowDetailsProps> =
({ isEnvelopeConnectedTo
}
}, [variableError]);
- useEffect(() => {
- if (isEnvelopeConnectedToChannel) {
- setData(workflowDetails);
- getAllJobs();
- }
- }, [isEnvelopeConnectedToChannel]);
+ useEffect(
+ () => {
+ if (isEnvelopeConnectedToChannel) {
+ setData(workflowDetails);
+ loadJobs();
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [isEnvelopeConnectedToChannel]
+ );
const handleSave = (): void => {
driver
diff --git
a/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx
b/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx
index f54261b947a..1b126ca33ca 100644
---
a/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx
+++
b/packages/runtime-tools-swf-enveloped-components/tests/components/WorkflowDetails.test.tsx
@@ -18,13 +18,22 @@
*/
import * as React from "react";
-import { render } from "@testing-library/react";
+import { render, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import WorkflowDetails from
"@kie-tools/runtime-tools-swf-enveloped-components/dist/workflowDetails/envelope/components/WorkflowDetails/WorkflowDetails";
-import { WorkflowInstance, WorkflowInstanceState } from
"@kie-tools/runtime-tools-swf-gateway-api/dist/types";
+import {
+ Job,
+ JobStatus,
+ WorkflowInstance,
+ WorkflowInstanceState,
+} from "@kie-tools/runtime-tools-swf-gateway-api/dist/types";
+
+jest.useFakeTimers();
const mockDriver = {
- jobsQuery: jest.fn(),
+ jobsQuery: jest.fn((_id: string) => {
+ return [{ ...sampleJob, expirationTime: new Date(Date.now() +
10000).toISOString() }];
+ }),
};
const sampleWorkflowDetails: WorkflowInstance = {
@@ -43,6 +52,25 @@ const sampleWorkflowDetails: WorkflowInstance = {
nodes: [],
};
+const sampleJob: Job = {
+ id: "a62d9d0a-87ea-4c13-87fb-67965d133020",
+ priority: 0,
+ lastUpdate: new Date("2024-10-30T15:31:46.709Z"),
+ workflowId: sampleWorkflowDetails.processId,
+ workflowInstanceId: sampleWorkflowDetails.id,
+ status: JobStatus.Scheduled,
+ expirationTime: new Date("2024-10-30T15:31:46.709Z"),
+ callbackEndpoint:
+
"http://localhost:4000/management/jobs/callback_state_timeouts/instances/9750c042-3fb2-40b7-96ba-ff10b6178c58/timers/-1",
+ repeatInterval: 0,
+ repeatLimit: 0,
+ scheduledId: "143",
+ retries: 0,
+ endpoint: "http://localhost:4000/jobs",
+ nodeInstanceId: "ee6c3f6e-8bc3-43dc-a249-dad1b19b52bb",
+ executionCounter: 0,
+};
+
describe("WorkflowDetails component", () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -86,4 +114,51 @@ describe("WorkflowDetails component", () => {
}
}
);
+
+ test("should render the job correctly", async () => {
+ const component = render(
+ <WorkflowDetails
+ isEnvelopeConnectedToChannel={true}
+ driver={mockDriver as any}
+ workflowDetails={sampleWorkflowDetails}
+ />
+ );
+
+ await waitFor(() =>
expect(mockDriver.jobsQuery).toHaveBeenCalledWith(sampleWorkflowDetails.id));
+
+ expect(component.queryByText("Jobs")).toBeInTheDocument();
+ expect(component.queryByText("Scheduled")).toBeInTheDocument();
+ expect(component.queryByText(sampleJob.id.slice(0,
7))).toBeInTheDocument();
+ });
+
+ test("should update sampleJob status to EXECUTED after 30 seconds", async ()
=> {
+ const component = render(
+ <WorkflowDetails
+ isEnvelopeConnectedToChannel={true}
+ driver={mockDriver as any}
+ workflowDetails={sampleWorkflowDetails}
+ />
+ );
+
+ await waitFor(() =>
expect(mockDriver.jobsQuery).toHaveBeenCalledWith(sampleWorkflowDetails.id));
+
+ expect(component.queryByText("Jobs")).toBeInTheDocument();
+ expect(component.queryByText("Scheduled")).toBeInTheDocument();
+ expect(component.queryByText(sampleJob.id.slice(0,
7))).toBeInTheDocument();
+
+ jest.advanceTimersByTime(10000);
+
+ await waitFor(() => {
+ expect(component.queryByText("Scheduled")).toBeInTheDocument();
+ });
+
+ mockDriver.jobsQuery.mockReturnValue([
+ { ...sampleJob, expirationTime: new
Date("2023-10-30T15:31:46.709Z").toISOString(), status: JobStatus.Executed },
+ ]);
+ jest.advanceTimersByTime(20000);
+
+ await waitFor(() => {
+ expect(component.queryByText("Executed")).toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/sonataflow-dev-app/README.md
b/packages/sonataflow-dev-app/README.md
index 6c734a6da97..1a60602207f 100644
--- a/packages/sonataflow-dev-app/README.md
+++ b/packages/sonataflow-dev-app/README.md
@@ -29,6 +29,26 @@ To run the development app, use the following command:
`pnpm start`
+### GraphQL Modifications
+
+This section covers modifications to the GraphQL database.
+
+## Changing Job Status to Executed
+
+To update a job's status to `"EXECUTED"`, use the following `curl` command.
Replace `{JOB_ID}` with the actual ID of the job you want to update.
+
+```bash
+curl -X POST http://localhost:4000/graphql \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "mutation JobExecute($id: String!) { JobExecute(id: $id)
}",
+ "variables": {
+ "id": "{JOB_ID}"
+ }
+ }'
+
+```
+
---
Apache KIE (incubating) is an effort undergoing incubation at The Apache
Software
diff --git a/packages/sonataflow-dev-app/src/MockData/graphql.js
b/packages/sonataflow-dev-app/src/MockData/graphql.js
index 33f4f6fd014..42d2b323399 100644
--- a/packages/sonataflow-dev-app/src/MockData/graphql.js
+++ b/packages/sonataflow-dev-app/src/MockData/graphql.js
@@ -18,6 +18,171 @@
*/
module.exports = {
ProcessInstanceData: [
+ {
+ id: "9750c042-3fb2-40b7-96ba-ff10b6178c58",
+ processId: "callback_state_timeouts",
+ processName: "Callback State Timeouts Example",
+ businessKey: null,
+ parentProcessInstanceId: null,
+ parentProcessInstance: null,
+ roles: [],
+ variables: null,
+ state: "ACTIVE",
+ start: "2024-10-30T15:31:46.571Z",
+ lastUpdate: "2024-10-30T15:31:46.571Z",
+ end: null,
+ addons: [
+ "kubernetes",
+ "microprofile-config-service-catalog",
+ "process-management",
+ "source-files",
+ "cloudevents",
+ "knative-eventing",
+ "knative-serving",
+ "jobs-knative-eventing",
+ "jobs-management",
+ ],
+ endpoint: "http://localhost:4000/callback_state_timeouts",
+ serviceUrl: "http://localhost:4000",
+ source:
+ '{\n "id": "callback_state_timeouts",\n "version": "1.0",\n "name":
"Callback State Timeouts Example",\n "description": "Simple process to show
the callback state timeout working",\n "start": "PrintStartMessage",\n
"events": [\n {\n "name": "callbackEvent",\n "source": "",\n
"type": "callback_event_type"\n }\n ],\n "functions": [\n {\n
"name": "systemOut",\n "type": "custom",\n "operation": "sysout"\n
}\n ],\n "states": [\n { [...]
+ error: null,
+ childProcessInstances: [],
+ nodes: [
+ {
+ id: "fe78615e-9aa0-4d08-9a3f-b67dbfcf5d9d",
+ nodeId: "9",
+ name: "CallbackState",
+ enter: "2024-10-30T15:31:46.566Z",
+ exit: null,
+ type: "CompositeContextNode",
+ definitionId: "9",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "7aaa5b45-ec4d-4267-8b8a-503cfc3e286b",
+ nodeId: "19",
+ name: "TimerNode_19",
+ enter: "2024-10-30T15:31:46.568Z",
+ exit: null,
+ type: "TimerNode",
+ definitionId: "19",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "e73715b4-d9de-49e8-b367-835e6fff3f53",
+ nodeId: "15",
+ name: "callbackEvent",
+ enter: "2024-10-30T15:31:46.569Z",
+ exit: null,
+ type: "EventNode",
+ definitionId: "15",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "ee6c3f6e-8bc3-43dc-a249-dad1b19b52bb",
+ nodeId: "17",
+ name: "EventSplit_17",
+ enter: "2024-10-30T15:31:46.567Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "Split",
+ definitionId: "17",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "e7f5fc8b-fb50-4983-b5dd-a0795bab324d",
+ nodeId: "13",
+ name: "Script",
+ enter: "2024-10-30T15:31:46.567Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "ActionNode",
+ definitionId: "13",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "7396e050-33c9-4096-bebd-7b579463c79a",
+ nodeId: "12",
+ name: "systemOut",
+ enter: "2024-10-30T15:31:46.566Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "ActionNode",
+ definitionId: "12",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "62dbdf7f-3922-411a-9cbe-201e1b2d0070",
+ nodeId: "10",
+ name: "EmbeddedStart",
+ enter: "2024-10-30T15:31:46.566Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "StartNode",
+ definitionId: "10",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "059f883b-cf4e-4cc2-8fc4-ea9a87ce55cf",
+ nodeId: "3",
+ name: "PrintStartMessage",
+ enter: "2024-10-30T15:31:46.559Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "CompositeContextNode",
+ definitionId: "3",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "9555d6b3-c00e-452a-964f-0a09799a7c74",
+ nodeId: "8",
+ name: "EmbeddedEnd",
+ enter: "2024-10-30T15:31:46.565Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "EndNode",
+ definitionId: "8",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "d337fe91-eeaa-4366-a87c-ceb4a1f80aa0",
+ nodeId: "7",
+ name: "Script",
+ enter: "2024-10-30T15:31:46.565Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "ActionNode",
+ definitionId: "7",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "5ba61e92-0683-4ecb-b686-58d45a169c3e",
+ nodeId: "6",
+ name: "systemOut",
+ enter: "2024-10-30T15:31:46.559Z",
+ exit: "2024-10-30T15:31:46.57Z",
+ type: "ActionNode",
+ definitionId: "6",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "42c5c1cd-960e-4380-af71-dced7c01cdbb",
+ nodeId: "4",
+ name: "EmbeddedStart",
+ enter: "2024-10-30T15:31:46.559Z",
+ exit: "2024-10-30T15:31:46.571Z",
+ type: "StartNode",
+ definitionId: "4",
+ __typename: "NodeInstance",
+ },
+ {
+ id: "40a009ca-35f3-483a-b87f-e479a51eba39",
+ nodeId: "1",
+ name: "Start",
+ enter: "2024-10-30T15:31:46.558Z",
+ exit: "2024-10-30T15:31:46.571Z",
+ type: "StartNode",
+ definitionId: "1",
+ __typename: "NodeInstance",
+ },
+ ],
+ milestones: [],
+ __typename: "ProcessInstance",
+ },
{
id: "e995b0d2-078a-488f-8346-0176ed8d5033",
processId: "service",
@@ -494,5 +659,46 @@ module.exports = {
__typename: "ProcessDefinition",
},
],
- JobsData: [],
+ JobsData: [
+ {
+ id: "a62d9d0a-87ea-4c13-87fb-67965d133020",
+ processId: "callback_state_timeouts",
+ processInstanceId: "9750c042-3fb2-40b7-96ba-ff10b6178c58",
+ rootProcessId: null,
+ status: "SCHEDULED",
+ expirationTime: () => new Date(Date.now() + 1 * 10 * 1000).toISOString(),
+ priority: 0,
+ callbackEndpoint:
+
"http://localhost:4000/management/jobs/callback_state_timeouts/instances/9750c042-3fb2-40b7-96ba-ff10b6178c58/timers/-1",
+ repeatInterval: 0,
+ repeatLimit: 0,
+ scheduledId: "143",
+ retries: 0,
+ lastUpdate: "2024-10-30T15:31:46.709Z",
+ endpoint: "http://localhost:4000/jobs",
+ nodeInstanceId: "ee6c3f6e-8bc3-43dc-a249-dad1b19b52bb",
+ executionCounter: 0,
+ __typename: "Job",
+ },
+ {
+ id: "e47fa096-8bc8-42c0-a66d-ad9b3b4b0d7f",
+ processId: "callback_state_timeouts",
+ processInstanceId: "9750c042-3fb2-40b7-96ba-ff10b6178c58",
+ rootProcessId: null,
+ status: "EXECUTED",
+ expirationTime: null,
+ priority: 0,
+ callbackEndpoint:
+
"http://localhost:4000/management/jobs/callback_state_timeouts/instances/d818e6dc-e949-4b11-b87b-678b614c0739/timers/-1",
+ repeatInterval: 0,
+ repeatLimit: 0,
+ scheduledId: "283",
+ retries: 0,
+ lastUpdate: "2024-10-30T16:27:22.201Z",
+ endpoint: "http://localhost:4000/jobs",
+ nodeInstanceId: "fb066c9c-4b25-42b6-a202-cbcdcac68d1b",
+ executionCounter: 1,
+ __typename: "Job",
+ },
+ ],
};
diff --git a/packages/sonataflow-dev-app/src/MockData/types.js
b/packages/sonataflow-dev-app/src/MockData/types.js
index cdf6c23ef90..eaa22f959dd 100644
--- a/packages/sonataflow-dev-app/src/MockData/types.js
+++ b/packages/sonataflow-dev-app/src/MockData/types.js
@@ -26,6 +26,10 @@ module.exports = typeDefs = gql`
query: Query
}
+ type Mutation {
+ JobExecute(id: String): String
+ }
+
type Query {
ProcessInstances(
where: ProcessInstanceArgument
diff --git a/packages/sonataflow-dev-app/src/server.js
b/packages/sonataflow-dev-app/src/server.js
index 4be09f446dd..f885872b496 100644
--- a/packages/sonataflow-dev-app/src/server.js
+++ b/packages/sonataflow-dev-app/src/server.js
@@ -112,6 +112,16 @@ function paginatedResult(arr, offset, limit) {
}
// Provide resolver functions for your schema fields
const resolvers = {
+ Mutation: {
+ JobExecute: async (_parent, args) => {
+ const job = data.JobsData.find((data) => {
+ return data.id === args["id"];
+ });
+ if (!job) return;
+ job.expirationTime = null;
+ job.status = "EXECUTED";
+ },
+ },
Query: {
ProcessInstances: async (parent, args) => {
let result = data.ProcessInstanceData.filter((datum) => {
@@ -169,7 +179,16 @@ const resolvers = {
await timeout();
return data.ProcessDefinitionData;
},
- Jobs: () => [],
+ Jobs: async (parent, args) =>
+ data.JobsData.filter((job) => {
+ if (!args["where"]) {
+ return true;
+ } else if (args["where"].processInstanceId &&
args["where"].processInstanceId.equal) {
+ return job.processInstanceId ==
args["where"].processInstanceId.equal;
+ } else {
+ return false;
+ }
+ }),
},
DateTime: new GraphQLScalarType({
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]