Skip to content

Commit

Permalink
ui: added generic error notification component
Browse files Browse the repository at this point in the history
addresses #118
  • Loading branch information
audrium committed Sep 15, 2020
1 parent 3b44264 commit e285872
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 73 deletions.
90 changes: 41 additions & 49 deletions reana-ui/src/actions.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/*
-*- coding: utf-8 -*-
-*- coding: utf-8 -*-
This file is part of REANA.
Copyright (C) 2020 CERN.
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 _ from "lodash";
import axios from "axios";

import { api } from "./config";
import { parseWorkflows, parseLogs, parseFiles } from "./util";
Expand All @@ -18,8 +19,12 @@ import {
getWorkflowSpecification
} from "./selectors";

export const ERROR = "Error";
export const CLEAR_ERROR = "Clear error";

export const CONFIG_FETCH = "Fetch app config info";
export const CONFIG_RECEIVED = "App config info received";
export const CONFIG_ERROR = "Fetch app config error";

export const USER_FETCH = "Fetch user authentication info";
export const USER_RECEIVED = "User info received";
Expand All @@ -33,9 +38,11 @@ export const USER_SIGN_ERROR = "User sign in/up error";
export const USER_SIGNEDOUT = "User signed out";
export const USER_REQUEST_TOKEN = "Request user token";
export const USER_TOKEN_REQUESTED = "User token requested";
export const USER_TOKEN_ERROR = "User token error";

export const WORKFLOWS_FETCH = "Fetch workflows info";
export const WORKFLOWS_RECEIVED = "Workflows info received";
export const WORKFLOWS_FETCH_ERROR = "Workflows fetch error";
export const WORKFLOW_LOGS_FETCH = "Fetch workflow logs";
export const WORKFLOW_LOGS_RECEIVED = "Workflow logs received";
export const WORKFLOW_FILES_FETCH = "Fetch workflow files";
Expand Down Expand Up @@ -68,20 +75,23 @@ const WORKFLOW_FILES_URL = (id, { page = 1, size }) => {
return url;
};

function errorActionCreator(error, name) {
const { status, data } = error?.response;
const { message } = data;
return { type: ERROR, name, status, message };
};

export const clearError = { type: CLEAR_ERROR };

export function loadConfig() {
return async dispatch => {
let resp, data;
try {
dispatch({ type: CONFIG_FETCH });
resp = await fetch(CONFIG_URL, { credentials: "include" });
} catch (err) {
throw new Error(CONFIG_URL, 0, err);
}
if (resp.ok) {
data = await resp.json();
}
dispatch({ type: CONFIG_RECEIVED, ...data });
return resp;
dispatch({ type: CONFIG_FETCH });
return await axios.get(CONFIG_URL, { withCredentials: true })
.then(resp => dispatch({ type: CONFIG_RECEIVED, ...resp.data }))
.catch(err => {
dispatch(errorActionCreator(err, CONFIG_URL));
dispatch({ type: CONFIG_ERROR });
});
};
}

Expand Down Expand Up @@ -159,47 +169,29 @@ export function userSignout() {

export function requestToken() {
return async dispatch => {
let resp, data;
try {
dispatch({ type: USER_REQUEST_TOKEN });
resp = await fetch(USER_REQUEST_TOKEN_URL, {
method: "PUT",
credentials: "include"
dispatch({ type: USER_REQUEST_TOKEN });
return await axios.put(USER_REQUEST_TOKEN_URL, null, { withCredentials: true })
.then(resp => dispatch({ type: USER_TOKEN_REQUESTED, ...resp.data }))
.catch(err => {
dispatch(errorActionCreator(err, USER_INFO_URL));
dispatch({ type: USER_TOKEN_ERROR });
});
} catch (err) {
throw new Error(USER_INFO_URL, 0, err);
}
if (resp.status === 401) {
data = await resp.json();
console.log(data.message);
} else if (resp.ok) {
data = await resp.json();
}
dispatch({ type: USER_TOKEN_REQUESTED, ...data });
return resp;
};
}

export function fetchWorkflows(pagination) {
return async dispatch => {
let resp, data;
try {
dispatch({ type: WORKFLOWS_FETCH });
resp = await fetch(WORKFLOWS_URL({ ...pagination }), {
credentials: "include"
dispatch({ type: WORKFLOWS_FETCH });
return await axios.get(WORKFLOWS_URL({ ...pagination }), { withCredentials: true })
.then(resp => dispatch({
type: WORKFLOWS_RECEIVED,
workflows: parseWorkflows(resp.data.items),
total: resp.data.total,
}))
.catch(err => {
dispatch(errorActionCreator(err, USER_INFO_URL));
dispatch({ type: WORKFLOWS_FETCH_ERROR });
});
} catch (err) {
throw new Error(USER_INFO_URL, 0, err);
}
if (resp.ok) {
data = await resp.json();
}
dispatch({
type: WORKFLOWS_RECEIVED,
workflows: parseWorkflows(data.items),
total: data.total
});
return resp;
};
}

Expand Down
64 changes: 64 additions & 0 deletions reana-ui/src/components/Notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
-*- 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, { useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Container, Message, Transition } from "semantic-ui-react";
import PropTypes from "prop-types";

import { clearError } from "../actions";
import { getError } from "../selectors";

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

const AUTO_CLOSE_TIMEOUT = 16000;

export default function Notification({ icon, header, message, closable }) {
const dispatch = useDispatch();
const error = useSelector(getError);
const timer = useRef(null);

const hide = () => dispatch(clearError);
const visible = message || error ? true : false;

if (closable && visible) {
clearTimeout(timer.current);
timer.current = setTimeout(() => hide(), AUTO_CLOSE_TIMEOUT);
}

return (
<Transition visible={visible} duration={300}>
<Container text className={styles.container}>
<Message
icon={icon}
header={header}
content={message || error?.message}
onDismiss={closable ? hide : null}
size="small"
error
/>
</Container>
</Transition >
);
}

Notification.propTypes = {
icon: PropTypes.string,
header: PropTypes.string,
message: PropTypes.string,
closable: PropTypes.bool
};

Notification.defaultProps = {
icon: "warning sign",
header: "An error has occurred",
message: null,
closable: true
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
under the terms of the MIT License; see LICENSE file for more details.
*/

@import '../../styles/palette';
@import '../styles/palette';

.warning {
.container {
margin-top: 2em;
}
1 change: 1 addition & 0 deletions reana-ui/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
export { default as Announcement } from "./Announcement";
export { default as CodeSnippet } from "./CodeSnippet";
export { default as Footer } from "./Footer";
export { default as Notification } from "./Notification";
export { default as Title } from "./Title";
export { default as TopHeader } from "./TopHeader";
export { default as TooltipIfTruncated } from "./TooltipIfTruncated";
3 changes: 2 additions & 1 deletion reana-ui/src/pages/BasePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/

import React from "react";
import { Announcement, Footer, TopHeader } from "../components";
import { Announcement, Notification, Footer, TopHeader } from "../components";

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

Expand All @@ -18,6 +18,7 @@ export default function BasePage({ children }) {
<div className={styles["reana-page"]}>
<Announcement />
<TopHeader />
<Notification />
<div className={styles["main"]}>{children}</div>
<Footer />
</div>
Expand Down
23 changes: 9 additions & 14 deletions reana-ui/src/pages/workflowDetails/WorkflowDetails.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
-*- coding: utf-8 -*-
-*- coding: utf-8 -*-
This file is part of REANA.
Copyright (C) 2020 CERN.
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.
Expand All @@ -11,20 +11,19 @@
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import { Container, Dimmer, Loader, Message, Tab } from "semantic-ui-react";
import { Container, Dimmer, Loader, Tab } from "semantic-ui-react";

import { fetchWorkflow } from "../../actions";
import { getWorkflow, loadingWorkflows, isWorkflowsFetched } from "../../selectors";
import BasePage from "../BasePage";
import { Notification } from "../../components";
import {
WorkflowInfo,
WorkflowLogs,
WorkflowFiles,
WorkflowSpecification
} from "./components";

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

export default function WorkflowDetailsPage() {
return (
<BasePage>
Expand Down Expand Up @@ -55,14 +54,10 @@ function WorkflowDetails() {

if (!workflow) {
return (
<Container text className={styles.warning}>
<Message
icon="warning sign"
header="Sorry, this workflow either does not exist or you are not authorised to see it."
size="small"
warning
/>
</Container>
<Notification
message="Sorry, this workflow either does not exist or you are not authorised to see it."
closable={false}
/>
);
}

Expand Down
6 changes: 2 additions & 4 deletions reana-ui/src/pages/workflowList/components/Welcome.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,7 @@ export function WelcomeNoTokenMsg() {
const loading = useSelector(loadingTokenStatus);
const dispatch = useDispatch();

const handleRequestToken = () => {
dispatch(requestToken());
};
const handleRequestToken = () => dispatch(requestToken());

return tokenStatus === "requested" ? (
<div>
Expand All @@ -134,7 +132,7 @@ export function WelcomeNoTokenMsg() {
<Button content="Token requested" disabled />
<small className={styles.requested}>
<em>
{moment.utc(tokenRequestedAt).format("Do MMM YYYY HH:mm [UTC]")}
{moment.utc(tokenRequestedAt).format("YYYY-MM-DDThh:mm:ss")}
</em>
</small>
</div>
Expand Down
Loading

0 comments on commit e285872

Please sign in to comment.