Skip to content

Commit 4dc0f6d

Browse files
authored
[FE] Implement 404/403s (#2966)
* Add SuspenseQueryComponent for the ErrorBoundary delegation , implement this component in all those pages where the issue was happening * Add Comment to the SuspenseQueryComponent * Create the Error page * Error page styling modifications * Error Page redirections * Redux Request handle error case and Navigation * SuspenseQueryComponent test suites * minor ErrorPage component modifications * Add error page test suites * SuspenseQueryComponent Error handling modification
1 parent e07ce28 commit 4dc0f6d

File tree

14 files changed

+202
-5
lines changed

14 files changed

+202
-5
lines changed

kafka-ui-react-app/src/components/App.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import React, { Suspense, useCallback } from 'react';
2-
import { Routes, Route, useLocation } from 'react-router-dom';
3-
import { clusterPath, getNonExactPath } from 'lib/paths';
2+
import { Routes, Route, useLocation, Navigate } from 'react-router-dom';
3+
import {
4+
accessErrorPage,
5+
clusterPath,
6+
errorPage,
7+
getNonExactPath,
8+
} from 'lib/paths';
49
import Nav from 'components/Nav/Nav';
510
import PageLoader from 'components/common/PageLoader/PageLoader';
611
import Dashboard from 'components/Dashboard/Dashboard';
@@ -20,6 +25,7 @@ import DiscordIcon from 'components/common/Icons/DiscordIcon';
2025
import ConfirmationModal from './common/ConfirmationModal/ConfirmationModal';
2126
import { ConfirmContextProvider } from './contexts/ConfirmContext';
2227
import { GlobalSettingsProvider } from './contexts/GlobalSettingsContext';
28+
import ErrorPage from './ErrorPage/ErrorPage';
2329

2430
const queryClient = new QueryClient({
2531
defaultOptions: {
@@ -123,6 +129,15 @@ const App: React.FC = () => {
123129
path={getNonExactPath(clusterPath())}
124130
element={<ClusterPage />}
125131
/>
132+
<Route
133+
path={accessErrorPage}
134+
element={<ErrorPage status={403} text="Access is Denied" />}
135+
/>
136+
<Route path={errorPage} element={<ErrorPage />} />
137+
<Route
138+
path="*"
139+
element={<Navigate to={errorPage} replace />}
140+
/>
126141
</Routes>
127142
</S.Container>
128143
<Toaster position="bottom-right" />

kafka-ui-react-app/src/components/Brokers/Brokers.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ import { Route, Routes } from 'react-router-dom';
33
import { getNonExactPath, RouteParams } from 'lib/paths';
44
import BrokersList from 'components/Brokers/BrokersList/BrokersList';
55
import Broker from 'components/Brokers/Broker/Broker';
6+
import SuspenseQueryComponent from 'components/common/SuspenseQueryComponent/SuspenseQueryComponent';
67

78
const Brokers: React.FC = () => (
89
<Routes>
910
<Route index element={<BrokersList />} />
10-
<Route path={getNonExactPath(RouteParams.brokerId)} element={<Broker />} />
11+
<Route
12+
path={getNonExactPath(RouteParams.brokerId)}
13+
element={
14+
<SuspenseQueryComponent>
15+
<Broker />
16+
</SuspenseQueryComponent>
17+
}
18+
/>
1119
</Routes>
1220
);
1321

kafka-ui-react-app/src/components/Connect/Connect.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
clusterConnectorsPath,
1010
} from 'lib/paths';
1111
import useAppParams from 'lib/hooks/useAppParams';
12+
import SuspenseQueryComponent from 'components/common/SuspenseQueryComponent/SuspenseQueryComponent';
1213

1314
import ListPage from './List/ListPage';
1415
import New from './New/New';
@@ -23,7 +24,11 @@ const Connect: React.FC = () => {
2324
<Route path={clusterConnectorNewRelativePath} element={<New />} />
2425
<Route
2526
path={getNonExactPath(clusterConnectConnectorRelativePath)}
26-
element={<DetailsPage />}
27+
element={
28+
<SuspenseQueryComponent>
29+
<DetailsPage />
30+
</SuspenseQueryComponent>
31+
}
2732
/>
2833
<Route
2934
path={clusterConnectConnectorsRelativePath}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import styled from 'styled-components';
2+
3+
export const Wrapper = styled.div`
4+
display: flex;
5+
justify-content: center;
6+
align-items: center;
7+
flex-direction: column;
8+
gap: 20px;
9+
margin-top: 100px;
10+
`;
11+
12+
export const Number = styled.div`
13+
font-size: 100px;
14+
color: ${({ theme }) => theme.errorPage.text};
15+
line-height: initial;
16+
`;
17+
18+
export const Text = styled.div`
19+
font-size: 20px;
20+
`;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
import { Button } from 'components/common/Button/Button';
3+
4+
import * as S from './ErrorPage.styled';
5+
6+
interface Props {
7+
status?: number;
8+
text?: string;
9+
btnText?: string;
10+
}
11+
12+
const ErrorPage: React.FC<Props> = ({
13+
status = 404,
14+
text = 'Page is not found',
15+
btnText = 'Go Back to Dashboard',
16+
}) => {
17+
return (
18+
<S.Wrapper>
19+
<S.Number>{status}</S.Number>
20+
<S.Text>{text}</S.Text>
21+
<Button buttonType="primary" buttonSize="M" to="/">
22+
{btnText}
23+
</Button>
24+
</S.Wrapper>
25+
);
26+
};
27+
28+
export default ErrorPage;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import { screen } from '@testing-library/react';
3+
import { render } from 'lib/testHelpers';
4+
import ErrorPage from 'components/ErrorPage/ErrorPage';
5+
6+
describe('ErrorPage', () => {
7+
it('should check Error Page rendering with default text', () => {
8+
render(<ErrorPage />);
9+
expect(screen.getByText('404')).toBeInTheDocument();
10+
expect(screen.getByText('Page is not found')).toBeInTheDocument();
11+
expect(screen.getByText('Go Back to Dashboard')).toBeInTheDocument();
12+
});
13+
it('should check Error Page rendering with custom text', () => {
14+
const props = {
15+
status: 403,
16+
text: 'access is denied',
17+
btnText: 'Go back',
18+
};
19+
render(<ErrorPage {...props} />);
20+
expect(screen.getByText(props.status)).toBeInTheDocument();
21+
expect(screen.getByText(props.text)).toBeInTheDocument();
22+
expect(screen.getByText(props.btnText)).toBeInTheDocument();
23+
});
24+
});

kafka-ui-react-app/src/components/Schemas/Details/Details.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
SCHEMA_LATEST_FETCH_ACTION,
2121
selectAllSchemaVersions,
2222
getSchemaLatest,
23+
getAreSchemaLatestRejected,
2324
} from 'redux/reducers/schemas/schemasSlice';
2425
import { showServerError } from 'lib/errorHandling';
2526
import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
@@ -55,6 +56,7 @@ const Details: React.FC = () => {
5556
const versions = useAppSelector((state) => selectAllSchemaVersions(state));
5657
const schema = useAppSelector(getSchemaLatest);
5758
const isFetched = useAppSelector(getAreSchemaLatestFulfilled);
59+
const isRejected = useAppSelector(getAreSchemaLatestRejected);
5860
const areVersionsFetched = useAppSelector(getAreSchemaVersionsFulfilled);
5961

6062
const columns = React.useMemo(
@@ -78,6 +80,10 @@ const Details: React.FC = () => {
7880
}
7981
};
8082

83+
if (isRejected) {
84+
navigate('/404');
85+
}
86+
8187
if (!isFetched || !schema) {
8288
return <PageLoader />;
8389
}

kafka-ui-react-app/src/components/Schemas/Edit/Edit.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
SCHEMA_LATEST_FETCH_ACTION,
2626
getAreSchemaLatestFulfilled,
2727
schemaUpdated,
28+
getAreSchemaLatestRejected,
2829
} from 'redux/reducers/schemas/schemasSlice';
2930
import PageLoader from 'components/common/PageLoader/PageLoader';
3031
import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
@@ -54,6 +55,7 @@ const Edit: React.FC = () => {
5455

5556
const schema = useAppSelector((state) => getSchemaLatest(state));
5657
const isFetched = useAppSelector(getAreSchemaLatestFulfilled);
58+
const isRejected = useAppSelector(getAreSchemaLatestRejected);
5759

5860
const formatedSchema = React.useMemo(() => {
5961
return schema?.schemaType === SchemaType.PROTOBUF
@@ -98,6 +100,10 @@ const Edit: React.FC = () => {
98100
}
99101
};
100102

103+
if (isRejected) {
104+
navigate('/404');
105+
}
106+
101107
if (!isFetched || !schema) {
102108
return <PageLoader />;
103109
}

kafka-ui-react-app/src/components/Topics/Topics.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getNonExactPath,
77
RouteParams,
88
} from 'lib/paths';
9+
import SuspenseQueryComponent from 'components/common/SuspenseQueryComponent/SuspenseQueryComponent';
910

1011
import New from './New/New';
1112
import ListPage from './List/ListPage';
@@ -16,7 +17,14 @@ const Topics: React.FC = () => (
1617
<Route index element={<ListPage />} />
1718
<Route path={clusterTopicNewRelativePath} element={<New />} />
1819
<Route path={clusterTopicCopyRelativePath} element={<New />} />
19-
<Route path={getNonExactPath(RouteParams.topicName)} element={<Topic />} />
20+
<Route
21+
path={getNonExactPath(RouteParams.topicName)}
22+
element={
23+
<SuspenseQueryComponent>
24+
<Topic />
25+
</SuspenseQueryComponent>
26+
}
27+
/>
2028
</Routes>
2129
);
2230

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { PropsWithChildren } from 'react';
2+
import { ErrorBoundary } from 'react-error-boundary';
3+
import { Navigate } from 'react-router-dom';
4+
5+
const ErrorComponent: React.FC<{ error: Error }> = ({ error }) => {
6+
const errorStatus = (error as unknown as Response)?.status
7+
? (error as unknown as Response).status
8+
: '404';
9+
10+
return <Navigate to={`/${errorStatus}`} />;
11+
};
12+
13+
/**
14+
* @description
15+
* basic idea that you can not choose a wrong url, that is why you are safe, but when
16+
* the user tries to manipulate some url to get the the desired result and the BE returns 404
17+
* it will be propagated to this component and redirected
18+
*
19+
* !!NOTE!! But only use this Component for GET query Throw error cause maybe in the future inner functionality may change
20+
* */
21+
const SuspenseQueryComponent: React.FC<PropsWithChildren<unknown>> = ({
22+
children,
23+
}) => {
24+
return (
25+
<ErrorBoundary FallbackComponent={ErrorComponent}>{children}</ErrorBoundary>
26+
);
27+
};
28+
29+
export default SuspenseQueryComponent;

0 commit comments

Comments
 (0)