Skip to content

Commit

Permalink
feat(ui-study): freeze study when blocking tasks running
Browse files Browse the repository at this point in the history
  • Loading branch information
skamril committed Oct 29, 2024
1 parent bbd1cd9 commit 85d3ae0
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 3 deletions.
180 changes: 180 additions & 0 deletions webapp/src/components/App/Singlestudy/FreezeStudy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Copyright (c) 2024, RTE (https://www.rte-france.com)
*
* See AUTHORS.txt
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*
* This file is part of the Antares project.
*/

import { StudyMetadata } from "@/common/types";
import { getTasks } from "@/services/api/tasks";
import { TaskStatus, TaskType } from "@/services/api/tasks/constants";
import type { TaskDTO, TTaskType } from "@/services/api/tasks/types";
import { WsChannel, WsEventType } from "@/services/webSocket/constants";
import { TaskEventPayload, WsEvent } from "@/services/webSocket/types";
import {
addWsEventListener,
removeWsEventListener,
subscribeWsChannels,
unsubscribeWsChannels,
} from "@/services/webSocket/ws";
import {
Backdrop,
Button,
List,
ListItem,
ListItemText,
Paper,
} from "@mui/material";
import { useEffect, useState } from "react";
import LinearProgressWithLabel from "@/components/common/LinearProgressWithLabel";
import { useTranslation } from "react-i18next";

const BLOCKING_TASK_TYPES = [
TaskType.UpgradeStudy,
TaskType.ThermalClusterSeriesGeneration,
] as const;

function getChannel(id: TaskDTO["id"]) {
return WsChannel.Task + id;
}

interface BlockingTask {
id: TaskDTO["id"];
type: TTaskType;
progress: number;
failed?: boolean;
}

interface Props {
studyId: StudyMetadata["id"];
}

function FreezeStudy({ studyId }: Props) {
const [blockingTasks, setBlockingTasks] = useState<BlockingTask[]>([]);
const { t } = useTranslation();
const hasLoadingTask = !!blockingTasks.find(
(task) => task.progress !== 100 && !task.failed,
);

useEffect(() => {
let ignore = false; // Prevent race condition

getTasks({
studyId,
type: [TaskType.UpgradeStudy, TaskType.ThermalClusterSeriesGeneration],
status: [TaskStatus.Pending, TaskStatus.Running],
}).then((tasks) => {
if (!ignore) {
setBlockingTasks(
tasks.map((task) => ({
id: task.id,
type: task.type!,
progress: -1,
})),
);

subscribeWsChannels(tasks.map(({ id }) => getChannel(id)));
}
});

const listener = (event: WsEvent) => {
console.log(event.type);
switch (event.type) {
case WsEventType.TaskAdded: {
const { id, type } = event.payload;

if (BLOCKING_TASK_TYPES.includes(type)) {
setBlockingTasks((tasks) => [
...tasks,
{
...event.payload,
progress: -1,
},
]);

subscribeWsChannels(getChannel(id));
}
break;
}
case WsEventType.TsGenerationProgress: {
setBlockingTasks((tasks) =>
tasks.map((task) =>
task.id === event.payload.task_id
? { ...task, progress: event.payload.progress }
: task,
),
);
break;
}
case WsEventType.TaskFailed: {
const { id } = event.payload;
setBlockingTasks((tasks) =>
tasks.map((task) =>
task.id === id ? { ...task, failed: true } : task,
),
);
unsubscribeWsChannels(getChannel(id));
break;
}
case WsEventType.TaskCompleted: {
const { id } = event.payload;
setBlockingTasks((tasks) =>
tasks.map((task) =>
task.id === id ? { ...task, progress: 100 } : task,
),
);
unsubscribeWsChannels(getChannel(id));
break;
}
}
};

addWsEventListener(listener);

return () => {
ignore = true;
removeWsEventListener(listener);
unsubscribeWsChannels();
};
}, [studyId]);

return (
<Backdrop open={blockingTasks.length > 0} sx={{ position: "absolute" }}>
<Paper sx={{ width: 500 }}>
<List dense>
{blockingTasks.map(({ id, type, progress, failed }) => (
<ListItem key={id}>
<ListItemText
primary={
<LinearProgressWithLabel
variant={progress === -1 ? "indeterminate" : "determinate"}
value={progress}
failed={failed}
/>
}
secondary={t(`tasks.type.${type}`)}
/>
</ListItem>
))}
</List>
{!hasLoadingTask && (
<Button
sx={{ m: 1, float: "right" }}
onClick={() => setBlockingTasks([])}
>
{t("global.close")}
</Button>
)}
</Paper>
</Backdrop>
);
}

export default FreezeStudy;
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import { useTranslation } from "react-i18next";
import usePromiseHandler from "../../../../../../hooks/usePromiseHandler";
import { generateTimeSeries } from "../../../../../../services/api/studies/timeseries";
import BuildIcon from "@mui/icons-material/Build";
import { useState } from "react";

function TimeSeriesManagement() {
const { study } = useOutletContext<{ study: StudyMetadata }>();
const { t } = useTranslation();
const [generationInProgress, setGenerationInProgress] = useState(false);

const handleGenerateTs = usePromiseHandler({
fn: generateTimeSeries,
Expand All @@ -45,9 +47,13 @@ function TimeSeriesManagement() {
return setTimeSeriesFormFields(study.id, data.dirtyValues);
};

const handleSubmitSuccessful = () => {
const handleSubmitSuccessful = async () => {
setGenerationInProgress(true);

// The WebSocket will trigger an event after the fulfillment of the promise
handleGenerateTs({ studyId: study.id });
await handleGenerateTs({ studyId: study.id });

setGenerationInProgress(false);
};

////////////////////////////////////////////////////////////////
Expand All @@ -57,7 +63,10 @@ function TimeSeriesManagement() {
return (
<Form
key={study.id}
config={{ defaultValues: () => getTimeSeriesFormFields(study.id) }}
config={{
defaultValues: () => getTimeSeriesFormFields(study.id),
disabled: generationInProgress,
}}
onSubmit={handleSubmit}
onSubmitSuccessful={handleSubmitSuccessful}
submitButtonText={t("study.configuration.tsManagement.generateTs")}
Expand Down
3 changes: 3 additions & 0 deletions webapp/src/components/App/Singlestudy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import CommandDrawer from "./Commands";
import { addWsEventListener } from "../../../services/webSocket/ws";
import useAppDispatch from "../../../redux/hooks/useAppDispatch";
import SimpleLoader from "../../common/loaders/SimpleLoader";
import FreezeStudy from "./FreezeStudy";
import { WsEvent } from "@/services/webSocket/types";
import { WsEventType } from "@/services/webSocket/constants";

Expand Down Expand Up @@ -186,12 +187,14 @@ function SingleStudy(props: Props) {
alignItems="center"
boxSizing="border-box"
overflow="hidden"
position="relative"
>
{isExplorer === true ? (
<TabWrapper study={study} border tabList={tabList} />
) : (
<HomeView study={study} tree={tree} />
)}
<FreezeStudy studyId={studyId!} />
</Box>
{openCommands && studyId && (
<CommandDrawer
Expand Down

0 comments on commit 85d3ae0

Please sign in to comment.