admin-dashboard-web/src/Pages/GroupTasks/Overview/GroupTasksViewModal.js

919 lines
30 KiB
JavaScript

import {
Alert,
Button,
Form,
Input,
InputNumber,
Popover,
Space,
Steps,
Tag,
notification,
} from "antd";
import { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
Constants,
FormatDatetime,
GetDuration,
getUserId,
hasXYPermission,
myFetch,
} from "../../../utils";
import {
CheckOutlined,
InfoCircleOutlined,
LockOutlined,
RetweetOutlined,
UndoOutlined,
} from "@ant-design/icons";
import TextArea from "antd/es/input/TextArea";
import { useTranslation } from "react-i18next";
import { MyAvatar } from "../../../Components/MyAvatar";
import MyModal, { MyNotFoundModalContent } from "../../../Components/MyModal";
import MyAttachments from "../../../Components/MyAttachments";
import { useWebSocketContext } from "../../../Contexts/WebSocketContext";
import { useGroupTasksContext } from "../../../Contexts/GroupTasksContext";
import { useAppContext } from "../../../Contexts/AppContext";
import {
GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
SentMessagesCommands,
} from "../../../Handlers/WebSocketMessageHandler";
export default function GroupTasksViewModal({ isOpen }) {
const webSocketContext = useWebSocketContext();
const appContext = useAppContext();
const groupTasksContext = useGroupTasksContext();
const navigate = useNavigate();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
let { paramCategory, paramGroupTaskId } = useParams();
const { t } = useTranslation();
const currentGroupTask = useRef(null);
const handleCancel = () => {
currentGroupTask.current = null;
navigate(`${Constants.ROUTE_PATHS.GROUP_TASKS}${paramCategory}`);
};
useEffect(() => {
if (!isOpen) return;
myFetch(
`/grouptasks/${paramCategory}/steps/${paramGroupTaskId}`,
"GET"
).then((data) => {
currentGroupTask.current = data.GroupTask;
groupTasksContext.setGroupTasksSteps(data.GroupTaskSteps);
});
}, [isOpen, paramCategory]);
if (!isOpen) return <></>;
const getAlertType = (status) => {
switch (status) {
case Constants.GROUP_TASKS_STATUS.FINISHED:
return "success";
case Constants.GROUP_TASKS_STATUS.FAILED:
return "error";
case Constants.GROUP_TASKS_STATUS.CANCELED:
case Constants.GROUP_TASKS_STATUS.PAUSED:
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
return "warning";
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
case Constants.GROUP_TASKS_STATUS.RUNNING:
default:
return "info";
}
};
const getAlertMessage = (status) => {
switch (status) {
case Constants.GROUP_TASKS_STATUS.FINISHED:
return t("groupTasks.groupTasksViewModal.alertMessage.successful");
case Constants.GROUP_TASKS_STATUS.RUNNING:
return t("groupTasks.groupTasksViewModal.alertMessage.taskIsRunning");
case Constants.GROUP_TASKS_STATUS.CANCELED:
return t("groupTasks.groupTasksViewModal.alertMessage.taskCanceled");
case Constants.GROUP_TASKS_STATUS.FAILED:
return t("groupTasks.groupTasksViewModal.alertMessage.taskFailed");
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
return t(
"groupTasks.groupTasksViewModal.alertMessage.taskInputRequired"
);
case Constants.GROUP_TASKS_STATUS.PAUSED:
return t("groupTasks.groupTasksViewModal.alertMessage.paused");
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
return t("groupTasks.groupTasksViewModal.alertMessage.undoEnded");
default:
return "Alert message not found";
}
};
const getStepItemStatus = (status) => {
switch (status) {
case Constants.GROUP_TASKS_STATUS.FINISHED:
return "finish";
case Constants.GROUP_TASKS_STATUS.RUNNING:
return "process";
case Constants.GROUP_TASKS_STATUS.CANCELED:
case Constants.GROUP_TASKS_STATUS.FAILED:
return "error";
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
case Constants.GROUP_TASKS_STATUS.PAUSED:
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
default:
return "wait";
}
};
const handleTaskFailedTryAgainRunTaskStep = (taskStepId, step) => {
webSocketContext.SendSocketMessage(
SentMessagesCommands.TaskFailedTryAgainRunTaskStep,
{
groupTaskId: currentGroupTask.current.Id,
category: currentGroupTask.current.Category,
groupId: currentGroupTask.current.GroupId,
step: step,
taskStepId: taskStepId,
}
);
};
const handleTaskContinueTaskStep = (taskStepId, step) => {
const groupTasksViewModalRequiredInputsForm = document.getElementById(
"groupTasksViewModalRequiredInputsForm"
);
let canTaskContinued = true;
let taskInputs = [];
if (groupTasksViewModalRequiredInputsForm !== null) {
const specifiedTaskInputs =
groupTasksViewModalRequiredInputsForm.getElementsByTagName("input");
if (specifiedTaskInputs.length > 0) {
for (let i = 0; i < specifiedTaskInputs.length; i++) {
if (specifiedTaskInputs[i].value === "") {
canTaskContinued = false;
break;
}
taskInputs.push({
parameterName:
specifiedTaskInputs[i].id.split(
"-"
)[6] /* Format: UUID-STEP-PARAMETER_NAME */,
value: specifiedTaskInputs[i].value,
});
}
}
const specifiedTaskTextareas =
groupTasksViewModalRequiredInputsForm.getElementsByTagName("textarea");
if (specifiedTaskTextareas.length > 0) {
for (let i = 0; i < specifiedTaskTextareas.length; i++) {
if (specifiedTaskTextareas[i].value === "") {
canTaskContinued = false;
break;
}
taskInputs.push({
parameterName:
specifiedTaskTextareas[i].id.split(
"-"
)[6] /* Format: UUID-STEP-PARAMETER_NAME */,
value: specifiedTaskTextareas[i].value,
});
}
}
}
if (!canTaskContinued) {
notificationApi["error"]({
message: t(
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.message"
),
description: t(
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.description"
),
});
return;
}
webSocketContext.SendSocketMessage(
SentMessagesCommands.TaskContinueTaskStep,
{
groupTaskId: currentGroupTask.current.Id,
category: currentGroupTask.current.Category,
groupId: currentGroupTask.current.GroupId,
step: step,
taskStepId: taskStepId,
taskInputs: taskInputs,
}
);
};
const handleUserActionTaskStep = (action, taskStepId, step) => {
webSocketContext.SendSocketMessage(
SentMessagesCommands.HandleUserActionTaskStep,
{
action: action,
groupTaskId: currentGroupTask.current.Id,
category: currentGroupTask.current.Category,
groupId: currentGroupTask.current.GroupId,
step: step,
taskStepId: taskStepId,
}
);
};
const ActionHandler = ({ status, taskStepId, index, taskLocked }) => {
const currentStepTask = groupTasksContext.categoryGroup.groups.find(
(group) => group.id === currentGroupTask.current.GroupId
).tasks[index];
switch (status) {
case Constants.GROUP_TASKS_STATUS.FAILED:
return (
<Button
size="small"
danger
disabled={taskLocked}
onClick={() =>
handleTaskFailedTryAgainRunTaskStep(taskStepId, index + 1)
}
>
{t("common.button.tryAgain")}
</Button>
);
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
return (
<Button
size="small"
disabled={taskLocked}
onClick={() => handleTaskContinueTaskStep(taskStepId, index + 1)}
>
{t("common.button.continue")}
</Button>
);
case Constants.GROUP_TASKS_STATUS.PAUSED:
return (
<Space>
{currentStepTask.repeatPossible && (
<Button
icon={<RetweetOutlined />}
size="small"
disabled={taskLocked}
onClick={() =>
handleUserActionTaskStep(2, taskStepId, index + 1)
}
>
{t("common.button.repeat")}
</Button>
)}
{currentStepTask.undoPossible && (
<Button
icon={<UndoOutlined />}
size="small"
disabled={taskLocked}
onClick={() =>
handleUserActionTaskStep(1, taskStepId, index + 1)
}
>
{t("common.button.undo")}
</Button>
)}
<Button
icon={<CheckOutlined />}
size="small"
type={
currentStepTask.repeatPossible && currentStepTask.undoPossible
? "primary"
: "default"
}
disabled={taskLocked}
onClick={() => handleUserActionTaskStep(0, taskStepId, index + 1)}
>
{t("common.button.resume")}
</Button>
</Space>
);
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
return (
<Button
size="small"
disabled={taskLocked}
onClick={() => handleUserActionTaskStep(2, taskStepId, index + 1)}
>
{t("common.button.resume")}
</Button>
);
default:
return <></>;
}
};
const stepsItemHandler = () => {
let groupTaskSteps = [];
let groupTasks = [];
groupTasksContext.groupTasksSteps.forEach((step) => {
if (step.GroupTasksId === paramGroupTaskId) {
groupTaskSteps.push(step);
}
});
groupTaskSteps.sort((a, b) => a.Step - b.Step);
groupTasksContext.categoryGroup.groups.forEach((group) => {
if (currentGroupTask.current.GroupId === group.id) {
groupTasks = group.tasks;
return;
}
});
const getStepItem = (groupTask, index) => {
return {
key: index,
title:
groupTaskSteps[index] !== undefined &&
groupTaskSteps[index].Inputs !== "" &&
groupTaskSteps[index].Status !==
Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED &&
groupTaskSteps[index].Status !==
Constants.GROUP_TASKS_STATUS.UNDO_ENDED ? (
<>
{groupTask.name}{" "}
<Popover
key={groupTaskSteps[index].Id + index}
trigger="click"
placement="bottomLeft"
title={
<h2
style={{
fontWeight: "bold",
color: Constants.COLORS.SECONDARY,
}}
>
{t(
"groupTasks.groupTasksViewModal.popover.specifiedTaskInputs"
)}
</h2>
}
content={
<>
{groupTask.parameters.length > 0 &&
groupTask.parameters.map((task) => {
return (
<span key={task.parameterName}>
<span style={{ fontWeight: "bold" }}>
{task.displayName}:{" "}
</span>
<span>
{
JSON.parse(groupTaskSteps[index].Inputs).find(
(input) =>
input.parameterName === task.parameterName
)?.value
}
</span>
<br />
</span>
);
})}
</>
}
>
<InfoCircleOutlined
style={{
fontSize: "18px",
color: Constants.COLORS.ICON_INFO,
marginBottom: "4px",
verticalAlign: "middle",
}}
/>
</Popover>{" "}
{groupTaskSteps[index]?.CreatorUserId !== undefined && (
<MyAvatar
tooltip
allUsers={appContext.users}
userId={groupTaskSteps[index].CreatorUserId}
/>
)}
</>
) : (
<>
{groupTask.name}{" "}
{groupTaskSteps[index]?.CreatorUserId !== undefined && (
<MyAvatar
tooltip
allUsers={appContext.users}
userId={groupTaskSteps[index].CreatorUserId}
/>
)}
</>
),
status:
groupTaskSteps[index] !== undefined
? getStepItemStatus(groupTaskSteps[index].Status)
: "wait",
description:
groupTaskSteps[index] !== undefined ? (
<>
<p style={{ color: "#000" }}>
<b>ID:</b> {groupTaskSteps[index].Id}
<br />
<b>{t("groupTasks.groupTasksViewModal.startedAt")}:</b>{" "}
{FormatDatetime(groupTaskSteps[index].StartedAt)}
<br />
<b>{t("groupTasks.groupTasksViewModal.endedAt")}:</b>{" "}
{groupTaskSteps[index].EndedAt !== "0001-01-01T00:00:00Z"
? FormatDatetime(groupTaskSteps[index].EndedAt)
: Constants.TEXT_EMPTY_PLACEHOLDER}
<br />
<b>{t("groupTasks.groupTasksViewModal.duration")}:</b>{" "}
{GetDuration(
groupTaskSteps[index].StartedAt,
groupTaskSteps[index].EndedAt
)}
</p>
<Alert
style={{ width: "100%" }}
message={
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<b>
{getAlertMessage(groupTaskSteps[index].Status)}{" "}
{groupTaskSteps[index].LockedByUserId !== "" && (
<>
<LockOutlined />{" "}
<MyAvatar
tooltip
allUsers={appContext.users}
userId={groupTaskSteps[index].LockedByUserId}
avatarWidth={24}
/>
</>
)}
</b>
<ActionHandler
status={groupTaskSteps[index].Status}
index={index}
taskLocked={groupTaskSteps[index].LockedByUserId}
taskStepId={groupTaskSteps[index].Id}
/>
</div>
}
description={
groupTaskSteps[index].Status ===
Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED ? (
<div id={`${currentGroupTask.current.Id}-scroll-${index}`}>
<InputRequiredHandler
webSocketContext={webSocketContext}
currentGroupTask={currentGroupTask.current}
groupTaskParameters={groupTask.parameters}
groupTaskStepInputs={
groupTaskSteps[index] !== undefined &&
groupTaskSteps[index].Inputs !== "" &&
groupTaskSteps[index].Inputs
}
notificationApi={notificationApi}
step={index + 1}
taskLockedByUserId={
groupTaskSteps[index].LockedByUserId
}
/>
</div>
) : (
groupTaskSteps[index].Log.length > 0 && (
<div
id={`${currentGroupTask.current.Id}-scroll-${index}`}
style={{ width: "100%" }}
>
<GroupTaskStepLogHandler
currentGroupTaskId={currentGroupTask.current.Id}
log={groupTaskSteps[index].Log}
files={groupTaskSteps[index].Files}
/>
</div>
)
)
}
type={getAlertType(groupTaskSteps[index].Status)}
showIcon
/>
</>
) : (
""
),
};
};
let stepItems = [];
groupTasks.forEach((groupTask, index) =>
stepItems.push(getStepItem(groupTask, index))
);
// occurs when tasks were taken from the group task config, but at a previous time the tasks existed
if (currentGroupTask.current.NumberOfSteps > groupTasks.length) {
for (
let i = groupTasks.length;
i < currentGroupTask.current.NumberOfSteps;
i++
) {
let stepParams = [];
if (groupTaskSteps[i].Inputs !== "") {
let params = JSON.parse(groupTaskSteps[i].Inputs);
for (let i2 = 0; i2 < params.length; i2++) {
params[i2].displayName = params[i2].parameterName;
}
stepParams = params;
}
stepItems.push(getStepItem({ name: "???", parameters: stepParams }, i));
}
}
return stepItems;
};
return (
<MyModal isOpen={isOpen} onCancel={handleCancel}>
{notificationContextHolder}
{!currentGroupTask.current ||
!hasXYPermission(
appContext.userPermissions,
Constants.PERMISSIONS.GROUP_TASKS.OVERVIEW.XYView,
currentGroupTask.current.Category
) ? (
<MyNotFoundModalContent
resultTitle={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
/>
) : (
<div key={paramGroupTaskId}>
<h1
style={{
color: Constants.COLORS.PRIMARY,
fontWeight: "bold",
marginBottom: 0,
}}
>
{currentGroupTask.current.GroupName}{" "}
<Popover
trigger="click"
title={
<h2
style={{
fontWeight: "bold",
color: Constants.COLORS.SECONDARY,
}}
>
{t("groupTasks.groupTasksViewModal.popover.details")}
</h2>
}
content={
<>
<p>
<span style={{ fontWeight: "bold" }}>ID:</span>{" "}
{paramGroupTaskId} <br />
<span style={{ fontWeight: "bold" }}>
{t("groupTasks.groupTasksViewModal.category")}:
</span>{" "}
{currentGroupTask.current.Category}
<br />
<span style={{ fontWeight: "bold" }}>
{t("groupTasks.groupTasksViewModal.startedAt")}:
</span>{" "}
{FormatDatetime(currentGroupTask.current.StartedAt)}
<br />
<span style={{ fontWeight: "bold" }}>
{t("groupTasks.groupTasksViewModal.endedAt")}:
</span>{" "}
{FormatDatetime(currentGroupTask.current.EndedAt)}
<br />
<span style={{ fontWeight: "bold" }}>
{t("groupTasks.groupTasksViewModal.duration")}:
</span>{" "}
{GetDuration(
currentGroupTask.current.StartedAt,
currentGroupTask.current.EndedAt
)}
</p>
{currentGroupTask.current.GlobalInputs !== "[]" ? (
<>
<h2
style={{
fontWeight: "bold",
color: Constants.COLORS.SECONDARY,
}}
>
{t(
"groupTasks.groupTasksViewModal.popover.specifiedGlobalInputs"
)}
</h2>
<span>
{JSON.parse(currentGroupTask.current.GlobalInputs).map(
(globalInput) => {
return (
<span
key={
globalInput.parameterName + globalInput.value
}
>
<span style={{ fontWeight: "bold" }}>
{
groupTasksContext.categoryGroup.groups
.find(
(group) =>
group.id ===
currentGroupTask.current.GroupId
)
.globalInputs.find(
(gI) =>
gI.parameterName ===
globalInput.parameterName
).displayName
}
:{" "}
</span>
<span>
{globalInput.value !== ""
? globalInput.value
: Constants.TEXT_EMPTY_PLACEHOLDER}
</span>
<br />
</span>
);
}
)}
</span>
</>
) : null}
</>
}
>
<InfoCircleOutlined
style={{
fontSize: "18px",
color: Constants.COLORS.ICON_INFO,
marginBottom: "4px",
verticalAlign: "middle",
}}
/>
</Popover>{" "}
<MyAvatar
tooltip
allUsers={appContext.users}
userId={currentGroupTask.current.CreatorUserId}
/>
<br />
</h1>
<div style={{ marginBottom: 16 }}>
<span>{currentGroupTask.current.Description}</span>
</div>
<Steps direction="vertical" items={stepsItemHandler()} />
</div>
)}
</MyModal>
);
}
function InputRequiredHandler({
webSocketContext,
currentGroupTask,
groupTaskParameters,
groupTaskStepInputs,
notificationApi,
step,
taskLockedByUserId,
}) {
const { t } = useTranslation();
const [inputFields, setInputFields] = useState({});
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
const stepInputs = JSON.parse(groupTaskStepInputs);
const getDefaultValue = (groupTaskParameter) => {
if (stepInputs !== false) {
const stepInput = stepInputs.find(
(stepInput) =>
stepInput.parameterName === groupTaskParameter.parameterName
)?.value;
if (stepInput) {
return stepInput;
}
}
if (globalInputs === undefined || !groupTaskParameter.global) return null;
const globalInputValue = globalInputs.find(
(globalInput) =>
globalInput.parameterName === groupTaskParameter.parameterName
);
return globalInputValue !== undefined ? globalInputValue.value : "";
};
const getLabel = (displayName, groupTaskParameter) => {
return groupTaskParameter.global ? (
<>
{displayName}
<Tag style={{ marginLeft: 6 }} color="purple">
{t("groupTasks.tag.global")}
</Tag>
</>
) : (
displayName
);
};
let lastChange = useRef();
let typingTimer = useRef();
const sendTypingMessage = (
currentGroupTaskId,
groupTaskParameterName,
inputValue
) => {
webSocketContext.SendSocketMessage(SentMessagesCommands.TaskLocking, {
element: `${currentGroupTaskId}-${step}-${groupTaskParameterName}`,
lockedByUserId: getUserId(),
groupTaskId: currentGroupTaskId,
parameterName: groupTaskParameterName,
value: inputValue,
step: step,
rememberId: GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
});
};
const onInputChange = (
inputValue,
currentGroupTaskId,
groupTaskParameterName
) => {
setInputFields((fields) => {
return ({ ...fields }[groupTaskParameterName] =
inputValue === null ? "" : inputValue);
});
if (taskLockedByUserId !== "") return;
if (Date.now() >= lastChange.current || lastChange.current === undefined) {
lastChange.current = Date.now() + 1000;
sendTypingMessage(currentGroupTaskId, groupTaskParameterName, inputValue);
} else {
clearTimeout(typingTimer.current);
}
typingTimer.current = setTimeout(
() =>
sendTypingMessage(
currentGroupTaskId,
groupTaskParameterName,
inputValue
),
1000
);
};
return (
<Form layout="vertical" id="groupTasksViewModalRequiredInputsForm">
{groupTaskParameters.map((groupTaskParameter) => {
switch (groupTaskParameter.type) {
case "text":
return (
<Form.Item
key={"fitem-" + groupTaskParameter.parameterName}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
)}
required
>
<Input
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""}
onChange={(e) =>
onInputChange(
e.target.value,
currentGroupTask.Id,
groupTaskParameter.parameterName
)
}
value={inputFields[groupTaskParameter.parameterName]}
/>
</Form.Item>
);
case "number":
return (
<Form.Item
key={"fitem-" + groupTaskParameter.parameterName}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
)}
required
>
<InputNumber
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
style={{ width: "100%" }}
defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""}
max={Number.MAX_SAFE_INTEGER}
onChange={(e) =>
onInputChange(
e,
currentGroupTask.Id,
groupTaskParameter.parameterName
)
}
value={inputFields[groupTaskParameter.parameterName]}
/>
</Form.Item>
);
case "textarea":
return (
<Form.Item
key={"fitem-" + groupTaskParameter.parameterName}
label={getLabel(
groupTaskParameter.displayName,
groupTaskParameter
)}
required
>
<TextArea
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""}
onChange={(e) =>
onInputChange(
e.target.value,
currentGroupTask.Id,
groupTaskParameter.parameterName
)
}
value={inputFields[groupTaskParameter.parameterName]}
/>
</Form.Item>
);
default:
notificationApi["error"]({
message: t(
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.message",
{ groupTaskParameterType: groupTaskParameter.type }
),
description: t(
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.description",
{
groupTaskParameterDisplayName: groupTaskParameter.displayName,
}
),
});
return (
<p>
Type <b>{groupTaskParameter.type}</b> not implemented. Was
specified in: <b>{groupTaskParameter.displayName}</b>
</p>
);
}
})}
</Form>
);
}
function GroupTaskStepLogHandler({ currentGroupTaskId, log, files }) {
return (
<span style={{ whiteSpace: "pre-line" }}>
{log}
{files !== "" && files !== " " && (
<MyAttachments
attachments={JSON.parse(files)}
downloadUrl={`${Constants.STATIC_CONTENT_ADDRESS}grouptasks/${currentGroupTaskId}/`}
/>
)}
</span>
);
}