Skip to content

Commit

Permalink
[9.102.x-prod] kie-issues#2656: [SonataFlow] Jobs List Status Continu…
Browse files Browse the repository at this point in the history
…es to Report "Expires in NN Minutes" After Job Expiration (apache#61)

* wip

* Refresh jobs list after 5 seconds
  • Loading branch information
fantonangeli authored Nov 12, 2024
1 parent b799e35 commit 02a187d
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -78,18 +84,44 @@ 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);
setIsLoading(false);
}
};

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 => {
Expand All @@ -101,20 +133,24 @@ const WorkflowDetails: React.FC<WorkflowDetailsProps> = ({ isEnvelopeConnectedTo
if (isEnvelopeConnectedToChannel) {
getVariableJSON();
}
}, [data]);
}, [data, isEnvelopeConnectedToChannel, workflowDetails.id]);

useEffect(() => {
if (variableError && variableError.length > 0) {
setErrorModalOpen(true);
}
}, [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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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();
Expand Down Expand Up @@ -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();
});
});
});
20 changes: 20 additions & 0 deletions packages/sonataflow-dev-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 02a187d

Please sign in to comment.