task locking
parent
837e7ee23d
commit
079e7d526d
|
@ -1,6 +1,5 @@
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Avatar,
|
|
||||||
Button,
|
Button,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
|
@ -10,10 +9,9 @@ import {
|
||||||
Result,
|
Result,
|
||||||
Steps,
|
Steps,
|
||||||
Tag,
|
Tag,
|
||||||
Tooltip,
|
|
||||||
notification,
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { useContext } from "react";
|
import { useContext, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Constants,
|
Constants,
|
||||||
|
@ -23,12 +21,9 @@ import {
|
||||||
GetDuration,
|
GetDuration,
|
||||||
MyAvatar,
|
MyAvatar,
|
||||||
getUserId,
|
getUserId,
|
||||||
|
setNativeValue,
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import {
|
import { InfoCircleOutlined, LockOutlined } from "@ant-design/icons";
|
||||||
AntDesignOutlined,
|
|
||||||
InfoCircleOutlined,
|
|
||||||
LockOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
|
|
||||||
export default function GroupTasksViewModal({ isOpen }) {
|
export default function GroupTasksViewModal({ isOpen }) {
|
||||||
const webSocketContext = useContext(WebSocketContext);
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
|
@ -102,7 +97,6 @@ export default function GroupTasksViewModal({ isOpen }) {
|
||||||
case Constants.GROUP_TASKS_STATUS.RUNNING:
|
case Constants.GROUP_TASKS_STATUS.RUNNING:
|
||||||
return "process";
|
return "process";
|
||||||
case Constants.GROUP_TASKS_STATUS.CANCELED:
|
case Constants.GROUP_TASKS_STATUS.CANCELED:
|
||||||
return "error";
|
|
||||||
case Constants.GROUP_TASKS_STATUS.FAILED:
|
case Constants.GROUP_TASKS_STATUS.FAILED:
|
||||||
return "error";
|
return "error";
|
||||||
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
|
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
|
||||||
|
@ -145,7 +139,10 @@ export default function GroupTasksViewModal({ isOpen }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
taskInputs.push({
|
taskInputs.push({
|
||||||
parameterName: specifiedTaskInputs[i].id,
|
parameterName:
|
||||||
|
specifiedTaskInputs[i].id.split(
|
||||||
|
"-"
|
||||||
|
)[6] /* Format: UUID-STEP-PARAMETER_NAME */,
|
||||||
value: specifiedTaskInputs[i].value,
|
value: specifiedTaskInputs[i].value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -528,6 +525,8 @@ function InputRequiredHandler({
|
||||||
step,
|
step,
|
||||||
taskLockedByUserId,
|
taskLockedByUserId,
|
||||||
}) {
|
}) {
|
||||||
|
const [inputFields, setInputFields] = useState({});
|
||||||
|
|
||||||
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
|
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
|
||||||
|
|
||||||
const getDefaultValue = (groupTaskParameter) => {
|
const getDefaultValue = (groupTaskParameter) => {
|
||||||
|
@ -552,18 +551,29 @@ function InputRequiredHandler({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastChanges = [];
|
let lastChanges = useRef([]);
|
||||||
|
let typingTimer = useRef();
|
||||||
|
|
||||||
const onInputChange = (currentGroupTaskId) => {
|
const onInputChange = (
|
||||||
lastChanges = lastChanges.filter(
|
inputValue,
|
||||||
|
currentGroupTaskId,
|
||||||
|
groupTaskParameterName
|
||||||
|
) => {
|
||||||
|
setInputFields((fields) => {
|
||||||
|
return ({ ...fields }[groupTaskParameterName] = inputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (taskLockedByUserId !== "") return;
|
||||||
|
|
||||||
|
lastChanges.current = lastChanges.current.filter(
|
||||||
(lc) =>
|
(lc) =>
|
||||||
Date.now() - new Date(lc.changeTime) < Constants.GROUP_TASK_LOCKED_TIME
|
Date.now() - new Date(lc.changeTime) < Constants.GROUP_TASK_LOCKED_TIME
|
||||||
);
|
);
|
||||||
|
|
||||||
const lastChange = lastChanges.find((lC) => lC.step === step);
|
const lastChange = lastChanges.current.find((lC) => lC.step === step);
|
||||||
|
|
||||||
if (lastChange === undefined) {
|
if (lastChange === undefined) {
|
||||||
lastChanges.push({
|
lastChanges.current.push({
|
||||||
step: step,
|
step: step,
|
||||||
changeTime: Date.now(),
|
changeTime: Date.now(),
|
||||||
});
|
});
|
||||||
|
@ -574,6 +584,19 @@ function InputRequiredHandler({
|
||||||
step: step,
|
step: step,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sync input value with other web clients
|
||||||
|
clearTimeout(typingTimer.current);
|
||||||
|
|
||||||
|
typingTimer.current = setTimeout(() => {
|
||||||
|
webSocketContext.SendSocketMessage(
|
||||||
|
SentMessagesCommands.UpdateGroupTaskStepUserInputValue,
|
||||||
|
{
|
||||||
|
element: `${currentGroupTaskId}-${step}-${groupTaskParameterName}`,
|
||||||
|
value: inputValue,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -592,15 +615,17 @@ function InputRequiredHandler({
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
key={"input-" + groupTaskParameter.parameterName}
|
key={"input-" + groupTaskParameter.parameterName}
|
||||||
id={groupTaskParameter.parameterName}
|
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
|
||||||
defaultValue={getDefaultValue(groupTaskParameter)}
|
defaultValue={getDefaultValue(groupTaskParameter)}
|
||||||
disabled={taskLockedByUserId !== ""}
|
disabled={taskLockedByUserId !== ""}
|
||||||
onChange={() =>
|
onChange={(e) =>
|
||||||
onInputChange(
|
onInputChange(
|
||||||
|
e.target.value,
|
||||||
currentGroupTask.Id,
|
currentGroupTask.Id,
|
||||||
groupTaskParameter.parameterName
|
groupTaskParameter.parameterName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
value={inputFields[groupTaskParameter.parameterName]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
@ -616,12 +641,14 @@ function InputRequiredHandler({
|
||||||
>
|
>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
key={"fitem-" + groupTaskParameter.parameterName}
|
key={"fitem-" + groupTaskParameter.parameterName}
|
||||||
id={groupTaskParameter.parameterName}
|
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
defaultValue={getDefaultValue(groupTaskParameter)}
|
defaultValue={getDefaultValue(groupTaskParameter)}
|
||||||
disabled={taskLockedByUserId !== ""}
|
disabled={taskLockedByUserId !== ""}
|
||||||
onChange={() =>
|
max={Number.MAX_SAFE_INTEGER}
|
||||||
|
onChange={(e) =>
|
||||||
onInputChange(
|
onInputChange(
|
||||||
|
e,
|
||||||
currentGroupTask.Id,
|
currentGroupTask.Id,
|
||||||
groupTaskParameter.parameterName
|
groupTaskParameter.parameterName
|
||||||
)
|
)
|
||||||
|
|
56
src/utils.js
56
src/utils.js
|
@ -1,6 +1,6 @@
|
||||||
import { UserOutlined } from "@ant-design/icons";
|
import { UserOutlined } from "@ant-design/icons";
|
||||||
import { Avatar, Badge, Tooltip } from "antd";
|
import { Avatar, Badge, Tooltip } from "antd";
|
||||||
import { createContext, useEffect, useRef, useState } from "react";
|
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ const ReceivedMessagesCommands = {
|
||||||
UpdateScannerLastUsed: 15,
|
UpdateScannerLastUsed: 15,
|
||||||
TaskLocked: 16,
|
TaskLocked: 16,
|
||||||
TaskUnlocked: 17,
|
TaskUnlocked: 17,
|
||||||
|
UpdateGroupTaskStepUserInputValue: 18,
|
||||||
};
|
};
|
||||||
|
|
||||||
// commands sent to the backend server
|
// commands sent to the backend server
|
||||||
|
@ -120,6 +121,7 @@ export const SentMessagesCommands = {
|
||||||
TaskContinueTaskStep: 3,
|
TaskContinueTaskStep: 3,
|
||||||
ReloadGroupTasks: 4,
|
ReloadGroupTasks: 4,
|
||||||
TaskLocking: 5,
|
TaskLocking: 5,
|
||||||
|
UpdateGroupTaskStepUserInputValue: 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function WebSocketProvider({
|
export function WebSocketProvider({
|
||||||
|
@ -146,21 +148,19 @@ export function WebSocketProvider({
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const StartGroupTasksOpenModalRememberIdRef = useRef(null);
|
const StartGroupTasksOpenModalRememberIdRef = useRef(null);
|
||||||
|
|
||||||
let socket = null;
|
//let socket = null;
|
||||||
const ws = useRef(null);
|
const ws = useRef(null);
|
||||||
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
socket = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession);
|
ws.current = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession);
|
||||||
|
|
||||||
ws.current = socket;
|
ws.current.onopen = () => {
|
||||||
|
|
||||||
socket.onopen = () => {
|
|
||||||
console.log("connected");
|
console.log("connected");
|
||||||
setConnectionBadgeStatus("success");
|
setConnectionBadgeStatus("success");
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
ws.current.onmessage = (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
console.log("received message", data);
|
console.log("received message", data);
|
||||||
|
|
||||||
|
@ -301,7 +301,6 @@ export function WebSocketProvider({
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ReceivedMessagesCommands.UpdateScannerLastUsed:
|
case ReceivedMessagesCommands.UpdateScannerLastUsed:
|
||||||
console.log("Received", body);
|
|
||||||
setScanners((arr) => {
|
setScanners((arr) => {
|
||||||
const newArr = [...arr];
|
const newArr = [...arr];
|
||||||
|
|
||||||
|
@ -313,7 +312,6 @@ export function WebSocketProvider({
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case ReceivedMessagesCommands.TaskLocked:
|
case ReceivedMessagesCommands.TaskLocked:
|
||||||
console.log("taskLocked", body);
|
|
||||||
setGroupTasksSteps((arr) => {
|
setGroupTasksSteps((arr) => {
|
||||||
const newArr = [...arr];
|
const newArr = [...arr];
|
||||||
|
|
||||||
|
@ -343,13 +341,21 @@ export function WebSocketProvider({
|
||||||
return newArr;
|
return newArr;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case ReceivedMessagesCommands.UpdateGroupTaskStepUserInputValue:
|
||||||
|
const foundInput = document.getElementById(body.element);
|
||||||
|
|
||||||
|
if (foundInput) {
|
||||||
|
setNativeValue(foundInput, body.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error("unknown command", cmd);
|
console.error("unknown command", cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onclose = (event) => {
|
ws.current.onclose = (event) => {
|
||||||
setIsReady(false);
|
setIsReady(false);
|
||||||
setConnectionBadgeStatus("error");
|
setConnectionBadgeStatus("error");
|
||||||
console.log("closed", event);
|
console.log("closed", event);
|
||||||
|
@ -374,7 +380,8 @@ export function WebSocketProvider({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
connect();
|
connect();
|
||||||
|
|
||||||
return () => socket.close();
|
//return () => socket.close();
|
||||||
|
return () => ws.current.close();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const SendSocketMessage = (cmd, body) => {
|
const SendSocketMessage = (cmd, body) => {
|
||||||
|
@ -406,6 +413,33 @@ export function WebSocketProvider({
|
||||||
</WebSocketContext.Provider>
|
</WebSocketContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
function doEvent(obj, event) {
|
||||||
|
var event = new Event(event, { target: obj, bubbles: true });
|
||||||
|
event.simulated = true;
|
||||||
|
|
||||||
|
let tracker = obj._valueTracker;
|
||||||
|
if (tracker) {
|
||||||
|
tracker.setValue(lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj ? obj.dispatchEvent(event) : false;
|
||||||
|
} */
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/52486921
|
||||||
|
function setNativeValue(element, value) {
|
||||||
|
let lastValue = element.value;
|
||||||
|
element.value = value;
|
||||||
|
let event = new Event("input", { target: element, bubbles: true });
|
||||||
|
// React 15
|
||||||
|
event.simulated = true;
|
||||||
|
// React 16
|
||||||
|
let tracker = element._valueTracker;
|
||||||
|
if (tracker) {
|
||||||
|
tracker.setValue(lastValue);
|
||||||
|
}
|
||||||
|
element.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
export function FormatDatetime(datetime) {
|
export function FormatDatetime(datetime) {
|
||||||
if (datetime === "0001-01-01T00:00:00Z") {
|
if (datetime === "0001-01-01T00:00:00Z") {
|
||||||
|
|
Loading…
Reference in New Issue