Skip to content

Commit

Permalink
More Namespace to Gateway changes and fixes (#1170)
Browse files Browse the repository at this point in the history
* scroll to top, fix redirect for gateway detail

* add return null

* scroll to top of details on nav from /list

* Cypress/ns to gw cli tests (#1158)

Cypress tests for new features in `gwa` CLI from NS to GW.

* replace Namespace with Gateway in org permission (#1159)

* Feature/ns to gw redirect (#1162)

* fix some capitlization

* fix no gw redirect handling and add toast

* more details on login

* show error for invalid display name format

* reset edit display name input on cancel

* avoid white bg on button hover if disabled

* match GatewayController activity in OrgController v3 (#1171)

* match GatewayController activity in OrgController v3

* openapi update
  • Loading branch information
rustyjux authored Sep 12, 2024
1 parent 76a0187 commit b95e4f5
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 29 deletions.
6 changes: 6 additions & 0 deletions e2e/cypress/tests/20-gateways/01-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ describe('My Gateways list page', () => {
});
})

it('Verify redirect to My Gateways page if no gateway selected', () => {
cy.visit(ns.detailPath)
cy.wait(2000)
cy.verifyToastMessage('First select a Gateway to view that page')
})

it('Check Gateway link goes to details page', () => {
cy.visit(ns.listPath)
cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click()
Expand Down
10 changes: 6 additions & 4 deletions src/controllers/v3/OrganizationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
removeKeys,
transformAllRefID,
syncRecordsThrowErrors,
parseBlobString,
} from '../../batch/feed-worker';
import {
GroupAccessService,
Expand Down Expand Up @@ -266,8 +267,8 @@ export class OrganizationController extends Controller {
/**
* > `Required Scope:` Namespace.Assign
*
* @summary Get administration activity for gateways associated with this Organization Unit
* @param orgUnit
* @summary Get administration activity for gateways associated with this Organization
* @param org
* @param first
* @param skip
* @returns Activity[]
Expand Down Expand Up @@ -299,8 +300,9 @@ export class OrganizationController extends Controller {
);

return transformActivity(records)
.map((o) => removeKeys(o, ['id']))
.map((o) => removeEmpty(o))
.map((o) => transformAllRefID(o, ['blob']))
.map((o) => parseJsonString(o, ['blob']));
.map((o) => parseJsonString(o, ['context']))
.map((o) => parseBlobString(o));
}
}
2 changes: 1 addition & 1 deletion src/controllers/v3/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1536,7 +1536,7 @@ paths:
$ref: '#/components/schemas/ActivityDetail'
type: array
description: '> `Required Scope:` Namespace.Assign'
summary: 'Get administration activity for gateways associated with this Organization Unit'
summary: 'Get administration activity for gateways associated with this Organization'
tags:
- Organizations
security:
Expand Down
40 changes: 32 additions & 8 deletions src/nextapp/components/edit-display-name/edit-display-name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
queryKey,
}) => {
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen, onOpen, onClose: originalOnClose } = useDisclosure();
const queryClient = useQueryClient();
const mutate = useApiMutation(mutation);
const [inputValue, setInputValue] = React.useState(data.displayName || '');
Expand All @@ -42,18 +42,24 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
);
const minCharLimit = 3;
const maxCharLimit = 30;
const [isValidFormat, setIsValidFormat] = React.useState(true);
const validNameRegex = /^[a-zA-Z0-9][\w\s().'/-]*$/;

const handleInputChange = (event) => {
const { value } = event.target;
setInputValue(value);
setCharCount(value.length);
setIsValidFormat(validNameRegex.test(value));
};

const form = React.useRef<HTMLFormElement>();
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (charCount >= minCharLimit && charCount <= maxCharLimit) {
if (charCount >= minCharLimit && charCount <= maxCharLimit && isValidFormat) {
updateNamespaceDisplayName();
}
};

const updateNamespaceDisplayName = async () => {
if (form.current) {
try {
Expand All @@ -62,7 +68,7 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
const entries = Object.fromEntries(formData);
await mutate.mutateAsync(entries);
queryClient.invalidateQueries(queryKey);
onClose();
originalOnClose();
toast({
title: 'Display name successfully edited',
status: 'success',
Expand All @@ -79,9 +85,21 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
}
}
};

const handleSaveClick = () => {
form.current?.requestSubmit();
};

const isInputValid = charCount >= minCharLimit && charCount <= maxCharLimit && isValidFormat;

// Add this new function to handle closing and resetting
const handleClose = () => {
setInputValue(data.displayName || '');
setCharCount(data.displayName?.length || 0);
setIsValidFormat(true);
originalOnClose();
};

return (
<>
<Button
Expand All @@ -94,7 +112,7 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
>
Edit
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<Modal isOpen={isOpen} onClose={handleClose}>
<ModalOverlay />
<ModalContent borderRadius="4px" px={11}>
<ModalHeader pt={10}>Edit display name</ModalHeader>
Expand All @@ -119,27 +137,32 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
onChange={handleInputChange}
name="displayName"
variant="bc-input"
isInvalid={charCount > maxCharLimit || charCount < minCharLimit}
isInvalid={!isInputValid}
data-testid="edit-display-name-input"
/>
{charCount > maxCharLimit && (
<Text color="bc-error" mt={2} mb={-8}>
You have reached the character limit
Display name must be less than 30 characters
</Text>
)}
{charCount < minCharLimit && (
<Text color="bc-error" mt={2} mb={-8}>
Display name must be at least 3 characters
</Text>
)}
{!isValidFormat && (charCount >= minCharLimit) && (charCount <= maxCharLimit) && (
<Text color="bc-error" mt={2}>
Display name must start with an alphanumeric character and can only use special characters "-()_ .'/"
</Text>
)}
</FormControl>
</form>
</ModalBody>
<ModalFooter pt={7} pb={7}>
<ButtonGroup>
<Button
px={7}
onClick={onClose}
onClick={handleClose}
variant="secondary"
data-testid="edit-display-name-cancel-btn"
>
Expand All @@ -149,7 +172,8 @@ const EditNamespaceDisplayName: React.FC<EditNamespaceDisplayNameProps> = ({
type="submit"
onClick={handleSaveClick}
data-testid="edit-display-name-submit-btn"
isDisabled={charCount > maxCharLimit || charCount < minCharLimit}
isDisabled={!isInputValid}
variant="primary"
>
Save
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ const GatewayGetStarted: React.FC<GatewayGetStartedProps> = ({
)}
<CliCommand
title='Log in'
description='Log in with your IDIR via the CLI.'
description='Log in with your IDIR via the CLI. Follow the prompts provided in the terminal to complete the login process.'
command='gwa login'
/>
<CliCommand
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { useToast } from '@chakra-ui/react';
import { useRouter } from 'next/router';

const GatewayToastHandler = () => {
const toast = useToast();
const router = useRouter();

React.useEffect(() => {
const handleRouteChange = () => {
const showToast = localStorage.getItem('showNoGatewayToast');
if (showToast === 'true') {
toast.closeAll();
toast({
title: `First select a Gateway to view that page`,
status: 'error',
isClosable: true,
duration: 5000,
});
localStorage.removeItem('showNoGatewayToast');
}
};

router.events.on('routeChangeComplete', handleRouteChange);

return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [toast, router]);

return null;
};

export default GatewayToastHandler;
41 changes: 34 additions & 7 deletions src/nextapp/components/no-gateway-redirect/no-gateway-redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,41 @@ import { useAuth } from '@/shared/services/auth';
const NoGatewayRedirect = () => {
const router = useRouter();
const { user } = useAuth();
const hasNamespace = !!user?.namespace;
const [isChecking, setIsChecking] = React.useState(true);

React.useEffect(() => {
if (!hasNamespace) {
router.push('/manager/gateways/list');
}
}, [hasNamespace]);
return null
const checkNamespaceAndRedirect = async () => {
// Wait for a short period to ensure user data is loaded
await new Promise(resolve => setTimeout(resolve, 2000));

setIsChecking(false);

if (!user?.namespace) {
try {
// Set localStorage item to show toast
await new Promise<void>((resolve, reject) => {
try {
localStorage.setItem('showNoGatewayToast', 'true');
resolve();
} catch (error) {
reject(error);
}
});
await router.push('/manager/gateways/list');
} catch (error) {
console.error('Error during redirect process:', error);
}
}
};

checkNamespaceAndRedirect();
}, [user, router]);

if (isChecking) {
return null; // could return a loading indicator
}

return null;
};

export default NoGatewayRedirect;
export default NoGatewayRedirect;
2 changes: 2 additions & 0 deletions src/nextapp/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import '@/shared/styles/global.css';
import { AppWrapper } from './context';
import '../../mocks';
import CompleteProfile from '@/components/complete-profile';
import GatewayToastHandler from '@/components/no-gateway-redirect/gateway-toast-handler';

const footerItems = [
{ href: 'http://www2.gov.bc.ca/gov/content/home', text: 'Home' },
Expand Down Expand Up @@ -122,6 +123,7 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
}}
>
<AppWrapper router={router}>
<GatewayToastHandler />
<Component {...pageProps} />
</AppWrapper>
</Box>
Expand Down
12 changes: 6 additions & 6 deletions src/nextapp/pages/manager/gateways/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const MyGatewaysPage: React.FC = () => {
const handleNamespaceChange = React.useCallback(
(namespace: Namespace) => async () => {
toast({
title: `Switching to gateway: ${namespace.displayName}`,
title: `Switching to Gateway: ${namespace.displayName}`,
status: 'info',
isClosable: true,
});
Expand All @@ -116,15 +116,15 @@ const MyGatewaysPage: React.FC = () => {
toast.closeAll();
client.invalidateQueries();
toast({
title: `Switched to gateway: ${namespace.displayName}`,
title: `Switched to Gateway: ${namespace.displayName}`,
status: 'success',
isClosable: true,
});
await router.push('/manager/gateways/detail');
} catch (err) {
toast.closeAll();
toast({
title: 'Unable to switch gateways',
title: 'Unable to switch Gateways',
status: 'error',
isClosable: true,
});
Expand Down Expand Up @@ -264,11 +264,11 @@ const MyGatewaysPage: React.FC = () => {
</Flex>
{isSuccess &&
(namespaceSearchResults.length === 1 ? (
<Text mb={4}>{namespaceSearchResults.length} gateway</Text>
<Text mb={4}>{namespaceSearchResults.length} Gateway</Text>
) : (
<Text mb={4}>{namespaceSearchResults.length} gateways</Text>
<Text mb={4}>{namespaceSearchResults.length} Gateways</Text>
))}
{isLoading && <Text mb={4}>Loading gateways...</Text>}
{isLoading && <Text mb={4}>Loading Gateways...</Text>}
{isError && <Text mb={4}>Gateways failed to load</Text>}
{isSuccess && (
<>
Expand Down
5 changes: 3 additions & 2 deletions src/nextapp/shared/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const _focus = {
boxShadow: 'sm',
};
const _disabled = {
opacity: 0.3,
opacity: 0.4,
cursor: 'not-allowed',
};
const _invalid = {
Expand Down Expand Up @@ -122,7 +122,8 @@ const buttonVariants = {
_disabled: {
..._disabled,
_hover: {
background: 'bc-blue',
bg: '#003366 !important', // Use the hex value of bc-blue directly, necessary oddly.
opacity: 0.4,
},
},
},
Expand Down

0 comments on commit b95e4f5

Please sign in to comment.