From d5e34e9334202ccc48e22a78c19cd682929dcea2 Mon Sep 17 00:00:00 2001 From: James Elson Date: Fri, 14 Jun 2024 14:03:00 -0700 Subject: [PATCH] My gateways page changes copied over --- .../namespace-manager/namespace-manager.tsx | 10 +- .../components/publishing-popover/index.ts | 1 + .../publishing-popover/publishing-popover.tsx | 120 ++++++ src/nextapp/pages/manager/gateways/index.tsx | 353 ++++++++++++++++++ 4 files changed, 479 insertions(+), 5 deletions(-) create mode 100644 src/nextapp/components/publishing-popover/index.ts create mode 100644 src/nextapp/components/publishing-popover/publishing-popover.tsx create mode 100644 src/nextapp/pages/manager/gateways/index.tsx diff --git a/src/nextapp/components/namespace-manager/namespace-manager.tsx b/src/nextapp/components/namespace-manager/namespace-manager.tsx index c846ebd93..a1ad8c5ed 100644 --- a/src/nextapp/components/namespace-manager/namespace-manager.tsx +++ b/src/nextapp/components/namespace-manager/namespace-manager.tsx @@ -90,10 +90,10 @@ const NamespaceManager: React.FC = ({ - Export Namespace Report + Export Gateway Report - Export a detailed report of your namespace metrics and + Export a detailed report of your gateway metrics and activities = ({ size="sm" data-testid="export-report-empty-text" > - You have no namespaces + You have no gateways - Create a namespace to manage. + Create a gateway to manage. )} @@ -181,7 +181,7 @@ const NamespaceManager: React.FC = ({ {isInvalid && ( - *Please select a namespace + *Please select a gateway )} diff --git a/src/nextapp/components/publishing-popover/index.ts b/src/nextapp/components/publishing-popover/index.ts new file mode 100644 index 000000000..be17cea1a --- /dev/null +++ b/src/nextapp/components/publishing-popover/index.ts @@ -0,0 +1 @@ +export { default } from './publishing-popover'; diff --git a/src/nextapp/components/publishing-popover/publishing-popover.tsx b/src/nextapp/components/publishing-popover/publishing-popover.tsx new file mode 100644 index 000000000..9cdcce5e9 --- /dev/null +++ b/src/nextapp/components/publishing-popover/publishing-popover.tsx @@ -0,0 +1,120 @@ +import * as React from 'react'; +import { + Icon, + Text, + Link, + Flex, + Center, + Popover, + PopoverTrigger, + PopoverContent, + PopoverBody, + PopoverArrow, +} from '@chakra-ui/react'; +import { FaClock, FaMinusCircle, FaCheckCircle } from 'react-icons/fa'; + +interface PublishingPopoverProps { + status: string; +} + +const PublishingPopover: React.FC = ({ status }) => { + return ( + <> + {status === 'disabled' && ( + + +
+ + + Publishing disabled + +
+
+ + + + + + + Publishing disabled + + + + This means you still don't have permission to publish to the API + directory any API contained in this gateway. Request publishing + permission by{' '} + + adding an organization + {' '} + to your gateway. + + + +
+ )} + {status === 'pending' && ( + + +
+ + + Pending publishing permission + +
+
+ + + + + + + Pending publishing permission + + + + This means you submitted a request to enable publishing + permission and is pending your Organization Administrator + approval. + + + +
+ )} + {status === 'enabled' && ( + + +
+ + + Publishing enabled + +
+
+ + + + + + + Publishing enabled + + + + This means you are now allowed to publish to the API directory + any API contained in this gateway, so others can find and access + them. + + + +
+ )} + + ); +}; + +export default PublishingPopover; diff --git a/src/nextapp/pages/manager/gateways/index.tsx b/src/nextapp/pages/manager/gateways/index.tsx new file mode 100644 index 000000000..bee01eb8f --- /dev/null +++ b/src/nextapp/pages/manager/gateways/index.tsx @@ -0,0 +1,353 @@ +import * as React from 'react'; +import { + Box, + Container, + Icon, + Heading, + Text, + Link, + useDisclosure, + Button, + Flex, + Spacer, + useToast, + Select, + Center, +} from '@chakra-ui/react'; +import Head from 'next/head'; +import { gql } from 'graphql-request'; +import { FaPlus, FaLaptopCode, FaRocket, FaServer } from 'react-icons/fa'; +import { useQueryClient } from 'react-query'; +import { differenceInDays } from 'date-fns'; + +import PageHeader from '@/components/page-header'; +import GridLayout from '@/layouts/grid'; +import Card from '@/components/card'; +import { restApi, useApi } from '@/shared/services/api'; +import NamespaceManager from '@/components/namespace-manager/namespace-manager'; +import { Namespace } from '@/shared/types/query.types'; +import SearchInput from '@/components/search-input'; +import PublishingPopover from '@/components/publishing-popover'; + +type GatewayActions = { + title: string; + url: string; + urlText: string; + icon: React.ComponentType; + description: string; + descriptionEnd: string; +}; + +const actions: GatewayActions[] = [ + { + title: 'Need to create a new gateway?', + url: + 'https://developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/tutorials/quick-start/', + urlText: 'API Provider Quick Start', + icon: FaPlus, + description: 'Follow our', + descriptionEnd: 'guide.', + }, + { + title: 'GWA CLI commands', + url: + 'https://developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/resources/gwa-commands/', + urlText: 'GWA CLI', + icon: FaLaptopCode, + description: 'Explore helpful commands in our', + descriptionEnd: 'guide.', + }, + { + title: 'Ready to deploy to production?', + url: + 'https://developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/guides/owner-journey-v1/#production-links', + urlText: 'going to production', + icon: FaRocket, + description: 'Check our', + descriptionEnd: 'checklist.', + }, +]; + +const MyGatewaysPage: React.FC = () => { + const managerDisclosure = useDisclosure(); + const { data, isLoading, isSuccess, isError } = useApi( + 'allNamespaces', + { query }, + { suspense: false } + ); + const today = new Date(); + + // Namespace change + const client = useQueryClient(); + const toast = useToast(); + const handleNamespaceChange = React.useCallback( + (namespace: Namespace) => async () => { + toast({ + title: `Switching to ${namespace.name} namespace`, + status: 'info', + isClosable: true, + }); + try { + await restApi(`/admin/switch/${namespace.id}`, { method: 'PUT' }); + toast.closeAll(); + client.invalidateQueries(); + toast({ + title: `Switched to ${namespace.name} namespace`, + status: 'success', + isClosable: true, + }); + } catch (err) { + toast.closeAll(); + toast({ + title: 'Unable to switch namespaces', + status: 'error', + isClosable: true, + }); + } + }, + [client, toast] + ); + + // Filtering + const [filter, setFilter] = React.useState(''); + const handleFilterChange = React.useCallback( + (event: React.ChangeEvent) => { + setFilter(event.target.value); + }, + [] + ); + + // Search + const [search, setSearch] = React.useState(''); + const handleSearchChange = (value: string) => { + setSearch(value); + }; + + // Filter and search results + const filterBySearch = (result) => { + const regex = new RegExp( + search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), + 'i' + ); + return result.filter( + (s) => regex.test(s.name) || regex.test(s.displayName) + ); + }; + const namespaceSearchResults = React.useMemo(() => { + const result = data?.allNamespaces ?? []; + if (search.trim()) { + if (filter === 'disabled') { + return filterBySearch(result).filter( + (s) => s.orgEnabled === false && !s.orgUpdatedAt + ); + } else if (filter === 'pending') { + return filterBySearch(result).filter( + (s) => s.orgEnabled === false && s.orgUpdatedAt + ); + } else if (filter === 'enabled') { + return filterBySearch(result).filter((s) => s.orgEnabled === true); + } else { + return filterBySearch(result); + } + } else { + if (filter === 'disabled') { + return result.filter((s) => s.orgEnabled === false && !s.orgUpdatedAt); + } else if (filter === 'pending') { + return result.filter((s) => s.orgEnabled === false && s.orgUpdatedAt); + } else if (filter === 'enabled') { + return result.filter((s) => s.orgEnabled === true); + } else { + return result; + } + } + }, [data, search, filter]); + + return ( + <> + + API Program Services | My Gateways + + + + Export Gateway Report + + } + title="My Gateways" + > + + {actions.map((action) => ( + + + + + + {action.title} + + + + {action.description}{' '} + + {action.urlText} + {' '} + {action.descriptionEnd} + + + + ))} + + + + + event.currentTarget.focus()} + onChange={handleSearchChange} + value={search} + data-testid="namespace-search-input" + /> + + {isSuccess && + (namespaceSearchResults.length === 1 ? ( + {namespaceSearchResults.length} gateway + ) : ( + {namespaceSearchResults.length} gateways + ))} + {isLoading && Loading gateways...} + {isError && Gateways failed to load} + {isSuccess && ( + <> + {namespaceSearchResults.map((namespace) => ( + + + + + + {namespace.displayName + ? namespace.displayName + : namespace.name} + + {differenceInDays( + today, + new Date(namespace.orgUpdatedAt) + ) <= 5 && ( +
+ + New + +
+ )} +
+ + {namespace.name} + +
+ + {namespace.orgEnabled === false && + !namespace.orgUpdatedAt && ( + + )} + {namespace.orgEnabled === false && namespace.orgUpdatedAt && ( + + )} + {namespace.orgEnabled === true && ( + + )} +
+ ))} + {namespaceSearchResults.length === 0 && ( + <> + + Empty folder + + + + No results found + + + + )} + + )} +
+
+ {data && ( + + )} + + ); +}; + +export default MyGatewaysPage; + +const query = gql` + query GetNamespaces { + allNamespaces { + id + name + displayName + orgEnabled + orgUpdatedAt + } + } +`;