Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: add a way to open, list and close interactive sessions #142

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions reana-ui/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export const WORKFLOW_SPECIFICATION_RECEIVED =
"Workflow specification received";
export const WORKFLOW_DELETE_INIT = "Initialize workflow deletion";
export const WORKFLOW_DELETED = "Workflow deleted";
export const OPEN_DELETE_WORKFLOW_MODAL = "Open delete workflow modal";
export const CLOSE_DELETE_WORKFLOW_MODAL = "Close delete workflow modal";
export const WORKFLOW_LIST_REFRESH = "Refresh workflow list";

const CONFIG_URL = `${api}/api/config`;
const USER_INFO_URL = `${api}/api/you`;
Expand All @@ -80,6 +83,12 @@ const WORKFLOW_FILES_URL = (id, pagination) =>
const WORKFLOW_SET_STATUS_URL = (id, status) =>
`${api}/api/workflows/${id}/status?${stringifyQueryParams(status)}`;

const INTERACTIVE_SESSIONS_OPEN_URL = (id, type = "jupyter") =>
`${api}/api/workflows/${id}/open/${type}`;

const INTERACTIVE_SESSIONS_CLOSE_URL = (id) =>
`${api}/api/workflows/${id}/close/`;

function errorActionCreator(error, name) {
const { status, data } = error?.response;
const { message } = data;
Expand Down Expand Up @@ -334,6 +343,7 @@ export function deleteWorkflow(id, workspace = false) {
)
.then((resp) => {
dispatch({ type: WORKFLOW_DELETED, ...resp.data });
dispatch({ type: WORKFLOW_LIST_REFRESH });
dispatch(triggerNotification("Success!", resp.data.message));
})
.catch((err) => {
Expand All @@ -346,3 +356,38 @@ export function deleteWorkflow(id, workspace = false) {
});
};
}

export function openDeleteWorkflowModal(workflow) {
return { type: OPEN_DELETE_WORKFLOW_MODAL, workflow };
}

export function closeDeleteWorkflowModal() {
return { type: CLOSE_DELETE_WORKFLOW_MODAL };
}

export function openInteractiveSession(id) {
return async (dispatch) => {
return await axios
.post(INTERACTIVE_SESSIONS_OPEN_URL(id), null, { withCredentials: true })
.then((resp) => {
dispatch({ type: WORKFLOW_LIST_REFRESH });
})
.catch((err) => {
dispatch(errorActionCreator(err, INTERACTIVE_SESSIONS_OPEN_URL(id)));
});
};
}

export function closeInteractiveSession(id) {
return async (dispatch) => {
return await axios
.post(INTERACTIVE_SESSIONS_CLOSE_URL(id), null, { withCredentials: true })
.then((resp) => {
dispatch({ type: WORKFLOW_LIST_REFRESH });
dispatch(triggerNotification("Success!", resp.data.message));
})
.catch((err) => {
dispatch(errorActionCreator(err, INTERACTIVE_SESSIONS_CLOSE_URL(id)));
});
};
}
68 changes: 68 additions & 0 deletions reana-ui/src/components/JupyterNotebookIcon.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@

@import "@palette";

:global(.icon).icon {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.icon {
display: inline-block;
vertical-align: middle;
}
134 changes: 134 additions & 0 deletions reana-ui/src/components/WorkflowActionsPopup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
-*- coding: utf-8 -*-

This file is part of REANA.
Copyright (C) 2020 CERN.

REANA is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
*/

import React, { useState } from "react";
import PropTypes from "prop-types";
import { Icon, Menu, Popup } from "semantic-ui-react";
import { useDispatch } from "react-redux";

import { workflowShape } from "~/props";
import {
deleteWorkflow,
openInteractiveSession,
closeInteractiveSession,
openDeleteWorkflowModal,
triggerNotification,
} from "~/actions";

import { JupyterNotebookIcon } from "~/components";

import styles from "./WorkflowActionsPopup.module.scss";

const JupyterIcon = <JupyterNotebookIcon className={styles["jupyter-icon"]} />;

export default function WorkflowActionsPopup({ workflow, className }) {
const dispatch = useDispatch();
const [open, setOpen] = useState(false);
const { id, size, status, session_status: sessionStatus } = workflow;
const isDeleted = status === "deleted";
const isDeletedUsingWorkspace = isDeleted && size !== "0K";
const isSessionOpen = sessionStatus === "created";

let menuItems = [];

if (!isDeleted && !isSessionOpen) {
menuItems.push({
key: "openNotebook",
content: "Open Jupyter Notebook",
icon: JupyterIcon,
onClick: (e) => {
dispatch(openInteractiveSession(id)).then(() => {
dispatch(
triggerNotification(
"Success!",
"The interactive session has been created. However, it could take several minutes to start the Jupyter Notebook."
)
);
});
setOpen(false);
e.stopPropagation();
},
});
}

if (isSessionOpen) {
menuItems.push({
key: "closeNotebook",
content: "Close Jupyter Notebook",
icon: JupyterIcon,
onClick: (e) => {
dispatch(closeInteractiveSession(id));
setOpen(false);
e.stopPropagation();
},
});
}

if (!isDeleted) {
menuItems.push({
key: "delete",
content: "Delete workflow",
icon: "trash",
onClick: (e) => {
dispatch(openDeleteWorkflowModal(workflow));
setOpen(false);
e.stopPropagation();
},
});
}

if (isDeletedUsingWorkspace) {
menuItems.push({
key: "freeup",
content: "Free up disk",
icon: "hdd",
onClick: (e) => {
dispatch(deleteWorkflow(id, true));
setOpen(false);
e.stopPropagation();
},
});
}

return (
<div className={className || styles.container}>
{(!isDeleted || isDeletedUsingWorkspace) && (
<Popup
basic
trigger={
<Icon
name="ellipsis vertical"
className={styles.icon}
onClick={(e) => {
setOpen(true);
e.stopPropagation();
}}
/>
}
position="bottom left"
on="click"
open={open}
onClose={() => setOpen(false)}
>
<Menu items={menuItems} secondary vertical />
</Popup>
)}
</div>
);
}

WorkflowActionsPopup.defaultProps = {
className: "",
};

WorkflowActionsPopup.propTypes = {
workflow: workflowShape.isRequired,
className: PropTypes.string,
};
34 changes: 34 additions & 0 deletions reana-ui/src/components/WorkflowActionsPopup.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
-*- coding: utf-8 -*-
This file is part of REANA.
Copyright (C) 2020 CERN.
REANA is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
*/

@import "@palette";

:global(.icon).icon {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}

.container {
cursor: pointer;
min-width: 22px;
margin-left: 2em;

&:hover {
color: darken($sepia, 30%);
}
}

.jupyter-icon {
width: 1.18em;
float: right;
margin: -0.2em 0em 0em 0.5em;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@
under the terms of the MIT License; see LICENSE file for more details.
*/

import PropTypes from "prop-types";
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { Button, Modal, Checkbox } from "semantic-ui-react";

import { workflowShape } from "~/props";
import { deleteWorkflow } from "~/actions";
import { deleteWorkflow, closeDeleteWorkflowModal } from "~/actions";
import {
getWorkflowDeleteModalOpen,
getWorkflowDeleteModalItem,
} from "~/selectors";

export default function WorkflowDeleteModal({
open,
workflow,
setOpenDeleteModal,
setSelectedWorkflow,
}) {
export default function WorkflowDeleteModal() {
const dispatch = useDispatch();
const [deleteWorkspace, setDeleteWorkspace] = useState(true);
const open = useSelector(getWorkflowDeleteModalOpen);
const workflow = useSelector(getWorkflowDeleteModalItem);

if (!workflow) return null;

const onCloseModal = () => {
setOpenDeleteModal(false);
setSelectedWorkflow({});
dispatch(closeDeleteWorkflowModal());
setDeleteWorkspace(true);
};

Expand Down Expand Up @@ -63,10 +63,3 @@ export default function WorkflowDeleteModal({
</Modal>
);
}

WorkflowDeleteModal.propTypes = {
open: PropTypes.bool.isRequired,
workflow: workflowShape.isRequired,
setOpenDeleteModal: PropTypes.func.isRequired,
setSelectedWorkflow: PropTypes.func.isRequired,
};
3 changes: 3 additions & 0 deletions reana-ui/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export { default as Pagination } from "./Pagination";
export { default as Title } from "./Title";
export { default as TopHeader } from "./TopHeader";
export { default as TooltipIfTruncated } from "./TooltipIfTruncated";
export { default as JupyterNotebookIcon } from "./JupyterNotebookIcon";
export { default as WorkflowDeleteModal } from "./WorkflowDeleteModal";
export { default as WorkflowActionsPopup } from "./WorkflowActionsPopup";
Loading