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]

Reply via email to