From d967d4adb031868ec8fad7da3369d37f327cf663 Mon Sep 17 00:00:00 2001
From: Thomas Heartman
Date: Tue, 20 Feb 2024 17:32:33 +0800
Subject: [PATCH] feat: add tabs (#6267)
This PR adds a new file "Application.tsx", which is analogous to the
existing Project.tsx file in that it contains the base layout for the
application page, as well as tabs pointing to sub pages. Currently, the
overview tab uses a paragraph with some fallback text, while the
connected instances table displays the instances table.
I have mostly copied the existing ApplicationEdit component and used
that as a base, assuming that we'll delete that component when we're
done with this.
---
.../src/component/application/Application.tsx | 238 ++++++++++++++++++
.../ApplicationEdit/ApplicationEdit.tsx | 9 -
.../__snapshots__/routes.test.tsx.snap | 2 +-
frontend/src/component/menu/routes.ts | 6 +-
4 files changed, 242 insertions(+), 13 deletions(-)
create mode 100644 frontend/src/component/application/Application.tsx
diff --git a/frontend/src/component/application/Application.tsx b/frontend/src/component/application/Application.tsx
new file mode 100644
index 0000000000..70066f326f
--- /dev/null
+++ b/frontend/src/component/application/Application.tsx
@@ -0,0 +1,238 @@
+/* eslint react/no-multi-comp:off */
+import React, { useContext, useState } from 'react';
+import {
+ Box,
+ Avatar,
+ Icon,
+ IconButton,
+ LinearProgress,
+ Link,
+ Tab,
+ Tabs,
+ Typography,
+ styled,
+} from '@mui/material';
+import { Link as LinkIcon } from '@mui/icons-material';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { UPDATE_APPLICATION } from 'component/providers/AccessProvider/permissions';
+import { ConnectedInstances } from './ConnectedInstances/ConnectedInstances';
+import { Dialogue } from 'component/common/Dialogue/Dialogue';
+import { PageContent } from 'component/common/PageContent/PageContent';
+import { PageHeader } from 'component/common/PageHeader/PageHeader';
+import AccessContext from 'contexts/AccessContext';
+import useApplicationsApi from 'hooks/api/actions/useApplicationsApi/useApplicationsApi';
+import useApplication from 'hooks/api/getters/useApplication/useApplication';
+import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
+import { useLocationSettings } from 'hooks/useLocationSettings';
+import useToast from 'hooks/useToast';
+import PermissionButton from 'component/common/PermissionButton/PermissionButton';
+import { formatDateYMD } from 'utils/formatDate';
+import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
+import { useUiFlag } from 'hooks/useUiFlag';
+import { ApplicationEdit } from './ApplicationEdit/ApplicationEdit';
+
+type Tab = {
+ title: string;
+ path: string;
+ name: string;
+};
+
+const StyledHeader = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: theme.shape.borderRadiusLarge,
+ marginBottom: theme.spacing(3),
+}));
+
+const TabContainer = styled('div')(({ theme }) => ({
+ padding: theme.spacing(0, 4),
+}));
+
+const Separator = styled('div')(({ theme }) => ({
+ width: '100%',
+ backgroundColor: theme.palette.divider,
+ height: '1px',
+}));
+
+const StyledTab = styled(Tab)(({ theme }) => ({
+ textTransform: 'none',
+ fontSize: theme.fontSizes.bodySize,
+ flexGrow: 1,
+ flexBasis: 0,
+ [theme.breakpoints.down('md')]: {
+ paddingLeft: theme.spacing(1),
+ paddingRight: theme.spacing(1),
+ },
+ [theme.breakpoints.up('md')]: {
+ minWidth: 160,
+ },
+}));
+
+export const Application = () => {
+ const useOldApplicationScreen = !useUiFlag('sdkReporting');
+ const navigate = useNavigate();
+ const name = useRequiredPathParam('name');
+ const { application, loading } = useApplication(name);
+ const { appName, url, description, icon = 'apps', createdAt } = application;
+ const { hasAccess } = useContext(AccessContext);
+ const { deleteApplication } = useApplicationsApi();
+ const { locationSettings } = useLocationSettings();
+ const { setToastData, setToastApiError } = useToast();
+ const { pathname } = useLocation();
+
+ if (useOldApplicationScreen) {
+ return ;
+ }
+
+ const basePath = `/applications/${name}`;
+
+ const [showDialog, setShowDialog] = useState(false);
+
+ const toggleModal = () => {
+ setShowDialog(!showDialog);
+ };
+
+ const formatDate = (v: string) => formatDateYMD(v, locationSettings.locale);
+
+ const onDeleteApplication = async (evt: React.SyntheticEvent) => {
+ evt.preventDefault();
+ try {
+ await deleteApplication(appName);
+ setToastData({
+ title: 'Deleted Successfully',
+ text: 'Application deleted successfully',
+ type: 'success',
+ });
+ navigate('/applications');
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ };
+
+ const renderModal = () => (
+
+ );
+
+ if (loading) {
+ return (
+
+ );
+ } else if (!application) {
+ return Application ({appName}) not found
;
+ }
+
+ const tabs: Tab[] = [
+ {
+ title: 'Overview',
+ path: basePath,
+ name: 'overview',
+ },
+ {
+ title: 'Connected instances',
+ path: `${basePath}/instances`,
+ name: 'instances',
+ },
+ ];
+
+ const newActiveTab = tabs.find((tab) => tab.path === pathname);
+
+ return (
+ <>
+
+
+
+
+ {icon || 'apps'}
+
+ {appName}
+
+ }
+ title={appName}
+ actions={
+ <>
+
+
+
+ }
+ />
+
+
+ Delete
+
+ >
+ }
+ />
+
+ ({ marginTop: theme.spacing(1) })}>
+
+ {description || ''}
+
+
+ Created: {formatDate(createdAt)}
+
+
+
+
+
+
+ {tabs.map((tab) => {
+ return (
+ navigate(tab.path)}
+ data-testid={`TAB_${tab.title}`}
+ />
+ );
+ })}
+
+
+
+
+ {renderModal()}}
+ />
+
+ } />
+ This is a placeholder
} />
+
+
+ >
+ );
+};
diff --git a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx
index aea2b3924e..7abc3e4a64 100644
--- a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx
+++ b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx
@@ -31,10 +31,8 @@ import { formatDateYMD } from 'utils/formatDate';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
-import { useUiFlag } from 'hooks/useUiFlag';
export const ApplicationEdit = () => {
- const showAdvancedApplicationMetrics = useUiFlag('sdkReporting');
const navigate = useNavigate();
const name = useRequiredPathParam('name');
const { application, loading } = useApplication(name);
@@ -87,13 +85,6 @@ export const ApplicationEdit = () => {
},
];
- if (showAdvancedApplicationMetrics) {
- tabData.push({
- label: 'Connected instances',
- component: ,
- });
- }
-
if (loading) {
return (
diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
index 434e846304..206cceae83 100644
--- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
+++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
@@ -144,7 +144,7 @@ exports[`returns all baseRoutes 1`] = `
"component": [Function],
"menu": {},
"parent": "/applications",
- "path": "/applications/:name",
+ "path": "/applications/:name/*",
"title": ":name",
"type": "protected",
},
diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts
index 2b4a6e2792..3bd675e294 100644
--- a/frontend/src/component/menu/routes.ts
+++ b/frontend/src/component/menu/routes.ts
@@ -17,7 +17,6 @@ import EditTagType from 'component/tags/EditTagType/EditTagType';
import CreateTagType from 'component/tags/CreateTagType/CreateTagType';
import CreateFeature from 'component/feature/CreateFeature/CreateFeature';
import EditFeature from 'component/feature/EditFeature/EditFeature';
-import { ApplicationEdit } from 'component/application/ApplicationEdit/ApplicationEdit';
import ContextList from 'component/context/ContextList/ContextList/ContextList';
import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectFeatureView';
import { CreateIntegration } from 'component/integrations/CreateIntegration/CreateIntegration';
@@ -47,6 +46,7 @@ import { ApplicationList } from '../application/ApplicationList/ApplicationList'
import { AddonRedirect } from 'component/integrations/AddonRedirect/AddonRedirect';
import { ExecutiveDashboard } from 'component/executiveDashboard/ExecutiveDashboard';
import { FeedbackList } from '../feedbackNew/FeedbackList';
+import { Application } from 'component/application/Application';
export const routes: IRoute[] = [
// Splash
@@ -163,10 +163,10 @@ export const routes: IRoute[] = [
// Applications
{
- path: '/applications/:name',
+ path: '/applications/:name/*',
title: ':name',
parent: '/applications',
- component: ApplicationEdit,
+ component: Application,
type: 'protected',
menu: {},
},