Skip to content

Commit

Permalink
ui: add way to open, list and close interactive sessions
Browse files Browse the repository at this point in the history
closes #77
  • Loading branch information
audrium committed Oct 21, 2020
1 parent 2d3b7a9 commit 6f37ddf
Show file tree
Hide file tree
Showing 18 changed files with 421 additions and 160 deletions.
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;
}
132 changes: 132 additions & 0 deletions reana-ui/src/components/WorkflowActionsPopup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
-*- 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 (isSessionOpen) {
menuItems.push({
key: "closeNotebook",
content: "Close Jupyter Notebook",
icon: JupyterIcon,
onClick: (e) => {
dispatch(closeInteractiveSession(id));
setOpen(false);
e.stopPropagation();
},
});
} else {
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 (isDeleted) {
if (isDeletedUsingWorkspace) {
menuItems.push({
key: "freeup",
content: "Free up disk",
icon: "hdd",
onClick: (e) => {
dispatch(deleteWorkflow(id, true));
setOpen(false);
e.stopPropagation();
},
});
}
} else {
menuItems.push({
key: "delete",
content: "Delete workflow",
icon: "trash",
onClick: (e) => {
dispatch(openDeleteWorkflowModal(workflow));
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

0 comments on commit 6f37ddf

Please sign in to comment.