From b4c5f498583c8520bb7b13caff9404dff40c97b5 Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Thu, 27 Aug 2020 13:20:43 +0200 Subject: [PATCH] Bugfix/link not found (#1296) Redirect to login page if anonymous tries to access a page without permission Co-authored-by: Eduard Heimbuch Co-authored-by: Sebastian Sdorra --- scm-ui/ui-components/.storybook/config.js | 3 + scm-ui/ui-components/.storybook/withRedux.js | 41 +++++++++++ scm-ui/ui-components/src/ErrorBoundary.tsx | 69 +++++++++++++++--- scm-ui/ui-components/src/ProtectedRoute.tsx | 17 +++-- scm-ui/ui-components/src/errors.ts | 4 ++ .../ui-webapp/src/admin/containers/Admin.tsx | 6 +- .../src/admin/containers/GlobalConfig.tsx | 4 +- .../plugins/containers/PluginsOverview.tsx | 13 ++-- .../roles/containers/SingleRepositoryRole.tsx | 4 +- scm-ui/ui-webapp/src/containers/Main.tsx | 70 ++++++++++--------- .../src/groups/containers/CreateGroup.tsx | 7 +- .../src/groups/containers/Groups.tsx | 4 +- .../src/groups/containers/SingleGroup.tsx | 4 +- scm-ui/ui-webapp/src/modules/indexResource.ts | 34 ++++++++- .../src/users/containers/CreateUser.tsx | 4 +- .../src/users/containers/SingleUser.tsx | 5 +- .../ui-webapp/src/users/containers/Users.tsx | 4 +- 17 files changed, 216 insertions(+), 77 deletions(-) create mode 100644 scm-ui/ui-components/.storybook/withRedux.js diff --git a/scm-ui/ui-components/.storybook/config.js b/scm-ui/ui-components/.storybook/config.js index ae409e6478..af032d4054 100644 --- a/scm-ui/ui-components/.storybook/config.js +++ b/scm-ui/ui-components/.storybook/config.js @@ -29,6 +29,7 @@ import { withI18next } from "storybook-addon-i18next"; import "!style-loader!css-loader!sass-loader!../../ui-styles/src/scm.scss"; import React from "react"; import { MemoryRouter } from "react-router-dom"; +import withRedux from "./withRedux"; let i18n = i18next; @@ -70,4 +71,6 @@ addDecorator( }) ); +addDecorator(withRedux); + configure(require.context("../src", true, /\.stories\.tsx?$/), module); diff --git a/scm-ui/ui-components/.storybook/withRedux.js b/scm-ui/ui-components/.storybook/withRedux.js new file mode 100644 index 0000000000..0abc2149ca --- /dev/null +++ b/scm-ui/ui-components/.storybook/withRedux.js @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from "react"; +import {createStore} from "redux"; +import { Provider } from 'react-redux' + +const reducer = (state, action) => { + return state; +}; + +const withRedux = (storyFn) => { + return React.createElement(Provider, { + store: createStore(reducer, {}), + children: storyFn() + }); +} + + +export default withRedux; diff --git a/scm-ui/ui-components/src/ErrorBoundary.tsx b/scm-ui/ui-components/src/ErrorBoundary.tsx index e8eda0d366..33c8a48494 100644 --- a/scm-ui/ui-components/src/ErrorBoundary.tsx +++ b/scm-ui/ui-components/src/ErrorBoundary.tsx @@ -21,14 +21,24 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { ReactNode } from "react"; +import React, { ComponentType, ReactNode } from "react"; import ErrorNotification from "./ErrorNotification"; +import { MissingLinkError } from "./errors"; +import { withContextPath } from "./urls"; +import { withRouter, RouteComponentProps } from "react-router-dom"; +import ErrorPage from "./ErrorPage"; +import { WithTranslation, withTranslation } from "react-i18next"; +import { compose } from "redux"; +import { connect } from "react-redux"; -type Props = { +type ExportedProps = { fallback?: React.ComponentType; children: ReactNode; + loginLink?: string; }; +type Props = WithTranslation & RouteComponentProps & ExportedProps; + type ErrorInfo = { componentStack: string; }; @@ -44,16 +54,44 @@ class ErrorBoundary extends React.Component { this.state = {}; } - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - // Catch errors in any components below and re-render with error message - this.setState({ - error, - errorInfo - }); + componentDidUpdate(prevProps: Readonly) { + // we must reset the error if the url has changed + if (this.state.error && prevProps.location !== this.props.location) { + this.setState({ error: undefined, errorInfo: undefined }); + } } + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + this.setState( + { + error, + errorInfo + }, + () => this.redirectToLogin(error) + ); + } + + redirectToLogin = (error: Error) => { + const { loginLink } = this.props; + if (error instanceof MissingLinkError) { + if (loginLink) { + window.location.assign(withContextPath("/login")); + } + } + }; + renderError = () => { + const { t } = this.props; + const { error } = this.state; + let FallbackComponent = this.props.fallback; + + if (error instanceof MissingLinkError) { + return ( + + ); + } + if (!FallbackComponent) { FallbackComponent = ErrorNotification; } @@ -69,4 +107,17 @@ class ErrorBoundary extends React.Component { return this.props.children; } } -export default ErrorBoundary; + +const mapStateToProps = (state: any) => { + const loginLink = state.indexResources?.links?.login?.href; + + return { + loginLink + }; +}; + +export default compose>( + withRouter, + withTranslation("commons"), + connect(mapStateToProps) +)(ErrorBoundary); diff --git a/scm-ui/ui-components/src/ProtectedRoute.tsx b/scm-ui/ui-components/src/ProtectedRoute.tsx index b83271fdb8..c371c49892 100644 --- a/scm-ui/ui-components/src/ProtectedRoute.tsx +++ b/scm-ui/ui-components/src/ProtectedRoute.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import React, { Component } from "react"; -import { Route, Redirect, withRouter, RouteComponentProps, RouteProps } from "react-router-dom"; +import { Redirect, Route, RouteComponentProps, RouteProps, withRouter } from "react-router-dom"; type Props = RouteComponentProps & RouteProps & { @@ -30,7 +30,16 @@ type Props = RouteComponentProps & }; class ProtectedRoute extends Component { - renderRoute = (Component: any, authenticated?: boolean) => { + constructor(props: Props) { + super(props); + this.state = { + error: undefined + }; + } + + renderRoute = (Component: any) => { + const { authenticated } = this.props; + return (routeProps: any) => { if (authenticated) { return ; @@ -50,8 +59,8 @@ class ProtectedRoute extends Component { }; render() { - const { component, authenticated, ...routeProps } = this.props; - return ; + const { component, ...routeProps } = this.props; + return ; } } diff --git a/scm-ui/ui-components/src/errors.ts b/scm-ui/ui-components/src/errors.ts index bf1f364e67..d1f7aef226 100644 --- a/scm-ui/ui-components/src/errors.ts +++ b/scm-ui/ui-components/src/errors.ts @@ -89,6 +89,10 @@ export class ConflictError extends BackendError { } } +export class MissingLinkError extends Error { + name = "MissingLinkError"; +} + export function createBackendError(content: BackendErrorContent, statusCode: number) { switch (statusCode) { case 404: diff --git a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx index 3d40359949..505a2760a1 100644 --- a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx @@ -29,12 +29,13 @@ import { Redirect, Route, RouteComponentProps, Switch } from "react-router-dom"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { Links } from "@scm-manager/ui-types"; import { + CustomQueryFlexWrappedColumns, NavLink, Page, - CustomQueryFlexWrappedColumns, PrimaryContentColumn, - SecondaryNavigationColumn, SecondaryNavigation, + SecondaryNavigationColumn, + StateMenuContextProvider, SubNavigation } from "@scm-manager/ui-components"; import { getAvailablePluginsLink, getInstalledPluginsLink, getLinks } from "../../modules/indexResource"; @@ -44,7 +45,6 @@ import GlobalConfig from "./GlobalConfig"; import RepositoryRoles from "../roles/containers/RepositoryRoles"; import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole"; import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole"; -import { StateMenuContextProvider } from "@scm-manager/ui-components"; type Props = RouteComponentProps & WithTranslation & { diff --git a/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx b/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx index bf82635458..73ebba7c6f 100644 --- a/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx @@ -26,7 +26,7 @@ import { WithTranslation, withTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Config, NamespaceStrategies } from "@scm-manager/ui-types"; import { ErrorNotification, Loading, Title } from "@scm-manager/ui-components"; -import { getConfigLink } from "../../modules/indexResource"; +import { mustGetConfigLink } from "../../modules/indexResource"; import { fetchConfig, getConfig, @@ -186,7 +186,7 @@ const mapStateToProps = (state: any) => { const config = getConfig(state); const configUpdatePermission = getConfigUpdatePermission(state); - const configLink = getConfigLink(state); + const configLink = mustGetConfigLink(state); const namespaceStrategies = getNamespaceStrategies(state); return { diff --git a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx index 7605495810..510ffcbb3d 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx @@ -25,7 +25,7 @@ import * as React from "react"; import { connect } from "react-redux"; import { WithTranslation, withTranslation } from "react-i18next"; import { compose } from "redux"; -import { PendingPlugins, PluginCollection } from "@scm-manager/ui-types"; +import { PendingPlugins, Plugin, PluginCollection } from "@scm-manager/ui-types"; import { Button, ButtonGroup, @@ -45,16 +45,15 @@ import { } from "../modules/plugins"; import PluginsList from "../components/PluginList"; import { - getAvailablePluginsLink, - getInstalledPluginsLink, - getPendingPluginsLink + getPendingPluginsLink, + mustGetAvailablePluginsLink, + mustGetInstalledPluginsLink } from "../../../modules/indexResource"; import PluginTopActions from "../components/PluginTopActions"; import PluginBottomActions from "../components/PluginBottomActions"; import ExecutePendingActionModal from "../components/ExecutePendingActionModal"; import CancelPendingActionModal from "../components/CancelPendingActionModal"; import UpdateAllActionModal from "../components/UpdateAllActionModal"; -import { Plugin } from "@scm-manager/ui-types"; import ShowPendingModal from "../components/ShowPendingModal"; type Props = WithTranslation & { @@ -319,8 +318,8 @@ const mapStateToProps = (state: any) => { const collection = getPluginCollection(state); const loading = isFetchPluginsPending(state); const error = getFetchPluginsFailure(state); - const availablePluginsLink = getAvailablePluginsLink(state); - const installedPluginsLink = getInstalledPluginsLink(state); + const availablePluginsLink = mustGetAvailablePluginsLink(state); + const installedPluginsLink = mustGetInstalledPluginsLink(state); const pendingPluginsLink = getPendingPluginsLink(state); const pendingPlugins = getPendingPlugins(state); diff --git a/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx b/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx index 4912dc5236..9829e91157 100644 --- a/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx +++ b/scm-ui/ui-webapp/src/admin/roles/containers/SingleRepositoryRole.tsx @@ -29,7 +29,7 @@ import { History } from "history"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { RepositoryRole } from "@scm-manager/ui-types"; import { ErrorPage, Loading, Title } from "@scm-manager/ui-components"; -import { getRepositoryRolesLink } from "../../../modules/indexResource"; +import { mustGetRepositoryRolesLink } from "../../../modules/indexResource"; import { fetchRoleByName, getFetchRoleFailure, getRoleByName, isFetchRolePending } from "../modules/roles"; import PermissionRoleDetail from "../components/PermissionRoleDetails"; import EditRepositoryRole from "./EditRepositoryRole"; @@ -107,7 +107,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { const role = getRoleByName(state, roleName); const loading = isFetchRolePending(state, roleName); const error = getFetchRoleFailure(state, roleName); - const repositoryRolesLink = getRepositoryRolesLink(state); + const repositoryRolesLink = mustGetRepositoryRolesLink(state); return { repositoryRolesLink, roleName, diff --git a/scm-ui/ui-webapp/src/containers/Main.tsx b/scm-ui/ui-webapp/src/containers/Main.tsx index 0f1a3966d5..ec72e372eb 100644 --- a/scm-ui/ui-webapp/src/containers/Main.tsx +++ b/scm-ui/ui-webapp/src/containers/Main.tsx @@ -31,7 +31,7 @@ import Users from "../users/containers/Users"; import Login from "../containers/Login"; import Logout from "../containers/Logout"; -import { ProtectedRoute } from "@scm-manager/ui-components"; +import { ProtectedRoute, ErrorBoundary } from "@scm-manager/ui-components"; import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; import CreateUser from "../users/containers/CreateUser"; @@ -68,39 +68,41 @@ class Main extends React.Component { url = "/login"; } return ( -
- - - - - - - - - - - - - - - - - - - - - - - -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
); } } diff --git a/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx index ceeeae20f8..2ac2de7a3f 100644 --- a/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx @@ -27,11 +27,10 @@ import { compose } from "redux"; import { WithTranslation, withTranslation } from "react-i18next"; import { History } from "history"; import { DisplayedUser, Group } from "@scm-manager/ui-types"; -import { Page } from "@scm-manager/ui-components"; -import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource"; +import { apiClient, Page } from "@scm-manager/ui-components"; +import { getUserAutoCompleteLink, mustGetGroupsLink } from "../../modules/indexResource"; import { createGroup, createGroupReset, getCreateGroupFailure, isCreateGroupPending } from "../modules/groups"; import GroupForm from "../components/GroupForm"; -import { apiClient } from "@scm-manager/ui-components"; type Props = WithTranslation & { createGroup: (link: string, group: Group, callback?: () => void) => void; @@ -97,7 +96,7 @@ const mapDispatchToProps = (dispatch: any) => { const mapStateToProps = (state: any) => { const loading = isCreateGroupPending(state); const error = getCreateGroupFailure(state); - const createLink = getGroupsLink(state); + const createLink = mustGetGroupsLink(state); const autocompleteLink = getUserAutoCompleteLink(state); return { createLink, diff --git a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx index c8a8a66233..7fe45d0163 100644 --- a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx @@ -35,7 +35,7 @@ import { PageActions, urls } from "@scm-manager/ui-components"; -import { getGroupsLink } from "../../modules/indexResource"; +import { mustGetGroupsLink } from "../../modules/indexResource"; import { fetchGroupsByPage, getFetchGroupsFailure, @@ -128,7 +128,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { const page = urls.getPageFromMatch(match); const canAddGroups = isPermittedToCreateGroups(state); const list = selectListAsCollection(state); - const groupLink = getGroupsLink(state); + const groupLink = mustGetGroupsLink(state); return { groups, diff --git a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx index 5b5555b80c..b38974cc0e 100644 --- a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx @@ -39,7 +39,7 @@ import { SubNavigation, StateMenuContextProvider } from "@scm-manager/ui-components"; -import { getGroupsLink } from "../../modules/indexResource"; +import { getGroupsLink, mustGetGroupsLink } from "../../modules/indexResource"; import { fetchGroupByName, getFetchGroupFailure, getGroupByName, isFetchGroupPending } from "../modules/groups"; import { Details } from "./../components/table"; import { EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks"; @@ -138,7 +138,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { const group = getGroupByName(state, name); const loading = isFetchGroupPending(state, name); const error = getFetchGroupFailure(state, name); - const groupLink = getGroupsLink(state); + const groupLink = mustGetGroupsLink(state); return { name, diff --git a/scm-ui/ui-webapp/src/modules/indexResource.ts b/scm-ui/ui-webapp/src/modules/indexResource.ts index d6e9a1e0e0..4aa12f8f7e 100644 --- a/scm-ui/ui-webapp/src/modules/indexResource.ts +++ b/scm-ui/ui-webapp/src/modules/indexResource.ts @@ -24,7 +24,7 @@ import * as types from "./types"; -import { apiClient } from "@scm-manager/ui-components"; +import { apiClient, MissingLinkError } from "@scm-manager/ui-components"; import { Action, IndexResources, Link } from "@scm-manager/ui-types"; import { isPending } from "./pending"; import { getFailure } from "./failure"; @@ -123,6 +123,14 @@ export function getLink(state: object, name: string) { } } +export function mustGetLink(state: object, name: string) { + const link = getLink(state, name); + if (link) { + return link; + } + throw new MissingLinkError(`No link in state for link name: '${name}'`); +} + export function getLinkCollection(state: object, name: string): Link[] { // @ts-ignore Right types not available if (state.indexResources.links && state.indexResources.links[name]) { @@ -145,10 +153,18 @@ export function getAvailablePluginsLink(state: object) { return getLink(state, "availablePlugins"); } +export function mustGetAvailablePluginsLink(state: object) { + return mustGetLink(state, "availablePlugins"); +} + export function getInstalledPluginsLink(state: object) { return getLink(state, "installedPlugins"); } +export function mustGetInstalledPluginsLink(state: object) { + return mustGetLink(state, "installedPlugins"); +} + export function getPendingPluginsLink(state: object) { return getLink(state, "pendingPlugins"); } @@ -169,10 +185,18 @@ export function getUsersLink(state: object) { return getLink(state, "users"); } +export function mustGetUsersLink(state: object) { + return mustGetLink(state, "users"); +} + export function getRepositoryRolesLink(state: object) { return getLink(state, "repositoryRoles"); } +export function mustGetRepositoryRolesLink(state: object) { + return mustGetLink(state, "repositoryRoles"); +} + export function getRepositoryVerbsLink(state: object) { return getLink(state, "repositoryVerbs"); } @@ -181,10 +205,18 @@ export function getGroupsLink(state: object) { return getLink(state, "groups"); } +export function mustGetGroupsLink(state: object) { + return mustGetLink(state, "groups"); +} + export function getConfigLink(state: object) { return getLink(state, "config"); } +export function mustGetConfigLink(state: object) { + return mustGetLink(state, "config"); +} + export function getRepositoriesLink(state: object) { return getLink(state, "repositories"); } diff --git a/scm-ui/ui-webapp/src/users/containers/CreateUser.tsx b/scm-ui/ui-webapp/src/users/containers/CreateUser.tsx index 455a2081df..6f86f3539b 100644 --- a/scm-ui/ui-webapp/src/users/containers/CreateUser.tsx +++ b/scm-ui/ui-webapp/src/users/containers/CreateUser.tsx @@ -28,7 +28,7 @@ import { WithTranslation, withTranslation } from "react-i18next"; import { History } from "history"; import { User } from "@scm-manager/ui-types"; import { Page } from "@scm-manager/ui-components"; -import { getUsersLink } from "../../modules/indexResource"; +import { mustGetUsersLink } from "../../modules/indexResource"; import { createUser, createUserReset, getCreateUserFailure, isCreateUserPending } from "../modules/users"; import UserForm from "../components/UserForm"; @@ -84,7 +84,7 @@ const mapDispatchToProps = (dispatch: any) => { const mapStateToProps = (state: any) => { const loading = isCreateUserPending(state); const error = getCreateUserFailure(state); - const usersLink = getUsersLink(state); + const usersLink = mustGetUsersLink(state); return { usersLink, loading, diff --git a/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx b/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx index ceea726797..e7d0856545 100644 --- a/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx +++ b/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx @@ -43,10 +43,9 @@ import EditUser from "./EditUser"; import { fetchUserByName, getFetchUserFailure, getUserByName, isFetchUserPending } from "../modules/users"; import { EditUserNavLink, SetPasswordNavLink, SetPermissionsNavLink, SetPublicKeysNavLink } from "./../components/navLinks"; import { WithTranslation, withTranslation } from "react-i18next"; -import { getUsersLink } from "../../modules/indexResource"; +import { mustGetUsersLink } from "../../modules/indexResource"; import SetUserPassword from "../components/SetUserPassword"; import SetPermissions from "../../permissions/components/SetPermissions"; -import AddPublicKey from "../components/publicKeys/AddPublicKey"; import SetPublicKeys from "../components/publicKeys/SetPublicKeys"; type Props = RouteComponentProps & @@ -148,7 +147,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { const user = getUserByName(state, name); const loading = isFetchUserPending(state, name); const error = getFetchUserFailure(state, name); - const usersLink = getUsersLink(state); + const usersLink = mustGetUsersLink(state); return { usersLink, name, diff --git a/scm-ui/ui-webapp/src/users/containers/Users.tsx b/scm-ui/ui-webapp/src/users/containers/Users.tsx index fe6a372c2b..d0d4adc9dc 100644 --- a/scm-ui/ui-webapp/src/users/containers/Users.tsx +++ b/scm-ui/ui-webapp/src/users/containers/Users.tsx @@ -35,7 +35,7 @@ import { PageActions, urls } from "@scm-manager/ui-components"; -import { getUsersLink } from "../../modules/indexResource"; +import { mustGetUsersLink } from "../../modules/indexResource"; import { fetchUsersByPage, getFetchUsersFailure, @@ -129,7 +129,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { const page = urls.getPageFromMatch(match); const canAddUsers = isPermittedToCreateUsers(state); const list = selectListAsCollection(state); - const usersLink = getUsersLink(state); + const usersLink = mustGetUsersLink(state); return { users,