Skip to content

Commit

Permalink
Merge branch 'maint-0.7'
Browse files Browse the repository at this point in the history
* maint-0.7:
  release: 0.7.2
  auth: hide SSO sign up notification
  ui: added privacy policy page with RoPo content
  auth: adjust success messages to user confirmation
  auth: clear notifications before sign in/up
  auth: display server confirmation success message
  auth: display notification when signing up via SSO
  auth: add route to handle email confirmation
  auth: show proper notifications when signing in/up
  ui: make notifications more generic
  ui: control signup form visibility with configs
  ui: show more meaningful errors
  ui: split Signin component into two separate components addresses reanahub/reana#469
  • Loading branch information
mvidalgarcia committed Feb 15, 2021
2 parents a551744 + 75f39be commit ec6ced8
Show file tree
Hide file tree
Showing 14 changed files with 653 additions and 92 deletions.
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ Version 0.8.0 (UNRELEASED)
- Adds filtering by status and search by name in workflow list page.
- Adds import aliases.

Version 0.7.2 (2021-02-04)
--------------------------

- Adds option to require user email confirmation after sign-up.
- Adds option to display CERN Privacy Notice for CERN installations.
- Changes notification system to improve sign-in and sign-up messages.

Version 0.7.1 (2020-11-24)
---------------------------

Expand Down
56 changes: 54 additions & 2 deletions reana-ui/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
stringifyQueryParams,
} from "~/util";
import {
getConfig,
getWorkflow,
getWorkflowLogs,
getWorkflowSpecification,
Expand All @@ -45,6 +46,9 @@ 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 USER_EMAIL_CONFIRMATION = "User request email confirmation";
export const USER_EMAIL_CONFIRMED = "User email confirmed";
export const USER_EMAIL_CONFIRMATION_ERROR = "User email confirmation error";

export const QUOTA_FETCH = "Fetch user quota info";
export const QUOTA_RECEIVED = "User quota info received";
Expand Down Expand Up @@ -72,6 +76,7 @@ const USER_SIGNUP_URL = `${api}/api/register`;
const USER_SIGNIN_URL = `${api}/api/login`;
const USER_SIGNOUT_URL = `${api}/api/logout`;
const USER_REQUEST_TOKEN_URL = `${api}/api/token`;
const USER_CONFIRM_EMAIL_URL = `${api}/api/confirm-email`;
const WORKFLOWS_URL = (params) =>
`${api}/api/workflows?verbose=true&${stringifyQueryParams(params)}`;
const WORKFLOW_LOGS_URL = (id) => `${api}/api/workflows/${id}/logs`;
Expand Down Expand Up @@ -149,20 +154,41 @@ export function loadUser() {
}

function userSignFactory(initAction, succeedAction, actionURL, body) {
return async (dispatch) => {
return async (dispatch, getStore) => {
const state = getStore();
const { userConfirmation } = getConfig(state);

dispatch({ type: initAction });
return await axios
.post(actionURL, body, {
withCredentials: true,
headers: { "Content-Type": "application/json" },
})
.then((resp) => {
dispatch(clearNotification);
dispatch({ type: succeedAction });
dispatch(loadUser());
if (initAction === USER_SIGNUP) {
dispatch(
triggerNotification(
"Success!",
`User registered. ${
userConfirmation
? "Please confirm your email by clicking on the link we sent you."
: ""
}`
)
);
}
return resp;
})
.catch((err) => {
dispatch({ type: USER_SIGN_ERROR, ...err.response.data });
// validation errors
if (err.response.data.errors) {
dispatch({ type: USER_SIGN_ERROR, ...err.response.data });
} else {
dispatch(errorActionCreator(err, USER_SIGN_ERROR));
}
return err;
});
};
Expand Down Expand Up @@ -203,6 +229,32 @@ export function requestToken() {
};
}

export function confirmUserEmail(token) {
return async (dispatch) => {
dispatch({ type: USER_EMAIL_CONFIRMATION });
return await axios
.post(
USER_CONFIRM_EMAIL_URL,
{ token: token },
{
withCredentials: true,
headers: { "Content-Type": "application/json" },
}
)
.then((resp) => {
dispatch({ type: USER_EMAIL_CONFIRMED });
dispatch(triggerNotification("Success!", resp.data?.message));
})
.catch((err) => {
// adapt error format coming from invenio-accounts (remove array)
if (Array.isArray(err.response?.data?.message)) {
err.response.data.message = err.response?.data?.message[0];
}
dispatch(errorActionCreator(err, USER_EMAIL_CONFIRMATION_ERROR));
});
};
}

export function fetchWorkflows(
pagination,
search,
Expand Down
23 changes: 19 additions & 4 deletions reana-ui/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@ import { Redirect, BrowserRouter, Route, Switch } from "react-router-dom";
import { useSelector } from "react-redux";
import { Dimmer, Loader } from "semantic-ui-react";

import { getUserFetchError, isSignedIn, loadingUser } from "~/selectors";
import {
getUserFetchError,
isSignedIn,
isSignupHidden,
loadingUser,
loadingConfig,
} from "~/selectors";
import Confirm from "~/pages/signin/Confirm";
import Signin from "~/pages/signin/Signin";
import Signup from "~/pages/signin/Signup";
import WorkflowList from "~/pages/workflowList/WorkflowList";
import WorkflowDetails from "~/pages/workflowDetails/WorkflowDetails";
import Profile from "~/pages/profile/Profile";
import PrivacyNotice from "~/pages/privacyNotice/PrivacyNotice";
import Error from "./Error";

import "./App.module.scss";
Expand All @@ -42,13 +51,15 @@ function ProtectedRoute(props) {
}

export default function App() {
const loading = useSelector(loadingUser);
const userLoading = useSelector(loadingUser);
const configLoading = useSelector(loadingConfig);
const loading = userLoading || configLoading;
const signedIn = useSelector(isSignedIn);
const signupHidden = useSelector(isSignupHidden);
const error = useSelector(getUserFetchError);
if (!isEmpty(error)) {
return <Error title={error.statusText} message={error.message} />;
}

return (
<BrowserRouter>
{loading ? (
Expand All @@ -63,8 +74,12 @@ export default function App() {
/>
<Route
path="/signup"
render={() => (signedIn ? <Redirect to="/" /> : <Signin signup />)}
render={() =>
signedIn || signupHidden ? <Redirect to="/" /> : <Signup />
}
/>
<Route path="/confirm/:token" component={Confirm} />
<Route path="/privacy-notice" component={PrivacyNotice} />
<ProtectedRoute exact path="/" component={WorkflowList} />
<ProtectedRoute path="/details/:id" component={WorkflowDetails} />
<ProtectedRoute path="/profile" component={Profile} />
Expand Down
8 changes: 7 additions & 1 deletion reana-ui/src/components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
*/

import React from "react";
import { Icon } from "semantic-ui-react";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { Icon } from "semantic-ui-react";

import { getConfig } from "~/selectors";

Expand All @@ -22,6 +23,11 @@ export default function Footer() {
<footer className={styles["footer-bottom"]}>
<span>Copyright © 2020 CERN</span>
<span className={styles["links"]}>
{config.cernRopo && (
<Link to="/privacy-notice">
<Icon name="privacy"></Icon> Privacy Notice
</Link>
)}
{config.docsURL && (
<a href={config.docsURL} target="_blank" rel="noopener noreferrer">
<Icon name="book"></Icon> Docs
Expand Down
32 changes: 32 additions & 0 deletions reana-ui/src/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
-*- 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 { useDispatch } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";

export function useSubmit(action) {
const dispatch = useDispatch();
const history = useHistory();
const location = useLocation();

const handleSubmit = (event, formData, setFormData) => {
const { from } = location.state || { from: { pathname: "/" } };
dispatch(action(formData)).then((res) => {
if (res.isAxiosError ?? false) {
setFormData({ ...formData, password: "" });
} else {
history.replace(from);
}
});
event.preventDefault();
};

return handleSubmit;
}
Loading

0 comments on commit ec6ced8

Please sign in to comment.