]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21397 Migrate 404 error page to the new UI
authorJeremy Davis <jeremy.davis@sonarsource.com>
Fri, 5 Jan 2024 14:16:50 +0000 (15:16 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 15 Jan 2024 20:03:06 +0000 (20:03 +0000)
server/sonar-web/src/main/js/app/components/NotFound.tsx
server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalAdminPageExtension-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/utils/startReactApp.tsx

index fb97fa303c0253b8e2c31185cf4809aed9eadbad..2eb7ca4f6cf6d9c3be11d75ebfbcefd16586b7ec 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { Card, CenteredLayout, Link, PageContentFontWrapper, Title } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import Link from '../../components/common/Link';
 import { translate } from '../../helpers/l10n';
-import SimpleContainer from './SimpleContainer';
 
-interface Props {
-  withContainer?: boolean;
-}
-
-export default function NotFound({ withContainer = true }: Props) {
-  const Container = withContainer ? SimpleContainer : React.Fragment;
+export default function NotFound() {
   return (
-    <Container>
+    <>
       <Helmet defaultTitle={translate('404_not_found')} defer={false} />
-      <div className="page-wrapper-simple" id="bd">
-        <div className="page-simple" id="nonav">
-          <h2 className="big-spacer-bottom">{translate('page_not_found')}</h2>
-          <p className="spacer-bottom">{translate('address_mistyped_or_page_moved')}</p>
-          <p>
-            <Link to="/">{translate('go_back_to_homepage')}</Link>
-          </p>
-        </div>
-      </div>
-    </Container>
+      <PageContentFontWrapper className="sw-body-md">
+        <CenteredLayout className="sw-flex sw-flex-col sw-items-center">
+          <Card className="sw-m-14 sw-w-abs-600">
+            <Title>{translate('page_not_found')}</Title>
+            <p className="sw-mb-2">{translate('address_mistyped_or_page_moved')}</p>
+            <p>
+              <Link to="/">{translate('go_back_to_homepage')}</Link>
+            </p>
+          </Card>
+        </CenteredLayout>
+      </PageContentFontWrapper>
+    </>
   );
 }
index 966a7add21e7ddc9f0830a1ae51c22081962900d..ed6214bc60880069e40ce7fcdde809bca19f2ab8 100644 (file)
@@ -27,6 +27,6 @@ export default function GlobalAdminPageExtension() {
   const { pluginKey, extensionKey } = useParams();
   const { adminPages } = useOutletContext<AdminPagesContext>();
 
-  const extension = (adminPages || []).find((p) => p.key === `${pluginKey}/${extensionKey}`);
-  return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
+  const extension = adminPages?.find((p) => p.key === `${pluginKey}/${extensionKey}`);
+  return extension ? <Extension extension={extension} /> : <NotFound />;
 }
index b271b37c54f738e5a8a5ebfdb24de0707e7e9afe..1fcedaa91b5886cebac67c33a15e3a68739d11e6 100644 (file)
@@ -20,8 +20,8 @@
 import * as React from 'react';
 import { useParams } from 'react-router-dom';
 import { AppState } from '../../../types/appstate';
-import withAppStateContext from '../app-state/withAppStateContext';
 import NotFound from '../NotFound';
+import withAppStateContext from '../app-state/withAppStateContext';
 import Extension from './Extension';
 
 export interface GlobalPageExtensionProps {
@@ -44,8 +44,8 @@ function GlobalPageExtension(props: GlobalPageExtensionProps) {
       ? `${params.pluginKey}/${params.extensionKey}`
       : `${pluginKey}/${extensionKey}`;
 
-  const extension = (globalPages || []).find((p) => p.key === fullKey);
-  return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
+  const extension = globalPages?.find((p) => p.key === fullKey);
+  return extension ? <Extension extension={extension} /> : <NotFound />;
 }
 
 export default withAppStateContext(GlobalPageExtension);
index cfcf315af9d2b8f2cad590c5c3713898d80bd6e5..2d97554b7d8c236dc01e881be1ad12a0ae2acbc6 100644 (file)
@@ -31,16 +31,13 @@ export default function ProjectAdminPageExtension() {
   // We keep that for compatibility but ideally should advocate to use tanstack query
   const onBranchesChange = useRefreshBranches();
 
-  const extension =
-    component &&
-    component.configuration &&
-    (component.configuration.extensions || []).find(
-      (p) => p.key === `${pluginKey}/${extensionKey}`,
-    );
+  const extension = component?.configuration?.extensions?.find(
+    (p) => p.key === `${pluginKey}/${extensionKey}`,
+  );
 
   return extension ? (
     <Extension extension={extension} options={{ component, onComponentChange, onBranchesChange }} />
   ) : (
-    <NotFound withContainer={false} />
+    <NotFound />
   );
 }
index ce7cf80205a83519c97a357a468ef10ce5103ecd..025a9a20529c4acd91d292c64db8356ba660f85a 100644 (file)
@@ -46,10 +46,10 @@ export default function ProjectPageExtension({ params }: ProjectPageExtensionPro
       ? `${params.pluginKey}/${params.extensionKey}`
       : `${pluginKey}/${extensionKey}`;
 
-  const extension = component.extensions && component.extensions.find((p) => p.key === fullKey);
+  const extension = component.extensions?.find((p) => p.key === fullKey);
   return extension ? (
     <Extension extension={extension} options={{ branchLike, component }} />
   ) : (
-    <NotFound withContainer={false} />
+    <NotFound />
   );
 }
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalAdminPageExtension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalAdminPageExtension-test.tsx
new file mode 100644 (file)
index 0000000..8ce78ba
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { screen } from '@testing-library/react';
+import * as React from 'react';
+import { Outlet, Route } from 'react-router-dom';
+import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
+import { AdminPagesContext } from '../../../../types/admin';
+import { Extension } from '../../../../types/types';
+import GlobalAdminPageExtension from '../GlobalAdminPageExtension';
+
+jest.mock('../Extension', () => ({
+  __esModule: true,
+  default(props: { extension: { key: string; name: string } }) {
+    return <h1>{props.extension.name}</h1>;
+  },
+}));
+
+const extensions = [{ key: 'plugin123/ext42', name: 'extension 42' }];
+
+it('should find the extension from params', () => {
+  renderGlobalAdminPageExtension('admin/extension/plugin123/ext42', extensions);
+
+  expect(screen.getByText('extension 42')).toBeInTheDocument();
+});
+
+it('should notify if extension is not found', () => {
+  renderGlobalAdminPageExtension('admin/extension/plugin123/wrong-extension', extensions);
+
+  expect(screen.getByText('page_not_found')).toBeInTheDocument();
+});
+
+function renderGlobalAdminPageExtension(navigateTo: string, adminPages: Extension[] = []) {
+  renderAppRoutes(
+    `admin/extension/:pluginKey/:extensionKey`,
+    () => (
+      <Route path="admin" element={<Wrapper adminPages={adminPages} />}>
+        <Route path="extension/:pluginKey/:extensionKey" element={<GlobalAdminPageExtension />} />
+      </Route>
+    ),
+    {
+      navigateTo,
+    },
+  );
+}
+
+function Wrapper(props: Readonly<AdminPagesContext>) {
+  return <Outlet context={props} />;
+}
index 30367d34051fca2567b97d717f0dfa04c2397e8d..b12aae3de3298f35f9d728ae47d339ed77d0edab 100644 (file)
@@ -239,8 +239,11 @@ const router = createBrowserRouter(
             path="admin/plugin_risk_consent"
             element={<PluginRiskConsent />}
           />
-          <Route path="not_found" element={<NotFound />} />
-          <Route path="*" element={<NotFound />} />
+
+          <Route element={<SimpleContainer />}>
+            <Route path="not_found" element={<NotFound />} />
+            <Route path="*" element={<NotFound />} />
+          </Route>
         </Route>
       </Route>
     </>,