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: added generic error notification component #120

Merged
merged 1 commit into from
Sep 16, 2020
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
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