Skip to content

Commit

Permalink
Fixes for S3 Onboarding (#2259)
Browse files Browse the repository at this point in the history
* Hide Launch console for EditingS3LogSource

* fix: Update link button and updated test for create

* test: Updated EditS3LogSource test

* design: fixing display of single card

* fix: disable editing of s3 bucket

* chore: updating text
  • Loading branch information
alexmylonas authored Dec 14, 2020
1 parent f65561c commit 5e22cc0
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 51 deletions.
24 changes: 16 additions & 8 deletions web/src/components/buttons/LinkButton/LinkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,29 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import React from 'react';
import { ButtonProps, Button, Box } from 'pouncejs';
import { ButtonProps, Button, Box, LinkProps } from 'pouncejs';
import { Link as RRLink, LinkProps as RRLinkProps } from 'react-router-dom';

type ButtonWithoutAs = Omit<ButtonProps, 'as'>;
type ToProp = Pick<RRLinkProps, 'to'>;

export type LinkButtonProps = ButtonWithoutAs & ToProp & { external?: boolean };

const LinkButton: React.FC<LinkButtonProps> = ({ external, to, children, ...rest }) => {
const linkProps = external
? { target: '_blank', rel: 'noopener noreferrer', href: to, as: 'a' as React.ElementType }
: { to, as: RRLink };

const LinkButton: React.FC<LinkButtonProps> = ({ disabled, external, to, children, ...rest }) => {
let linkProps: LinkProps;
if (disabled) {
linkProps = { as: 'span' as React.ElementType };
} else if (!external) {
linkProps = { to, as: RRLink };
} else {
linkProps = {
target: '_blank',
rel: 'noopener noreferrer',
href: to as string,
as: 'a' as React.ElementType,
};
}
return (
<Box
{...linkProps}
Expand All @@ -41,7 +49,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({ external, to, children, ...rest
},
}}
>
<Button as="span" {...rest}>
<Button as="span" aria-disabled={disabled} {...rest}>
{children}
</Button>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ const S3SourceConfigurationPanel: React.FC = () => {
name="s3Bucket"
as={FormikTextInput}
label="Bucket Name"
required
placeholder="The name of the S3 bucket that holds the logs"
disabled={!!initialValues.integrationId}
required
/>
<Field
as={FormikMultiCombobox}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { Text, Box, Flex, SimpleGrid, Card, Img, Heading, Button, useSnackbar } from 'pouncejs';
import { Text, Box, Flex, Card, Img, Heading, Button, useSnackbar } from 'pouncejs';
import React from 'react';
import { downloadData, toStackNameFormat } from 'Helpers/utils';
import { useFormikContext } from 'formik';
Expand All @@ -32,7 +32,7 @@ const StackDeployment: React.FC = () => {
const { pushSnackbar } = useSnackbar();
const { goToNextStep } = useWizardContext();
const { initialValues, values } = useFormikContext<S3LogSourceWizardValues>();
const { data, loading } = useGetLogCfnTemplate({
const { data, loading, error } = useGetLogCfnTemplate({
variables: {
input: {
awsAccountId: pantherConfig.AWS_ACCOUNT_ID,
Expand Down Expand Up @@ -63,60 +63,70 @@ const StackDeployment: React.FC = () => {
title="Deploy Panther's IAM roles"
subtitle="These roles will allow Panther to read your logs from the S3 Bucket"
/>
<SimpleGrid columns={2} gap={5} px={80} mx="auto" mb={6}>
<Card variant="dark" p={6}>
<Flex direction="column" align="center" spacing={4}>
<Img src={lightningIllustration} alt="Lightning" nativeWidth={40} nativeHeight={40} />
<Heading as="h4" size="x-small">
Using Cloudformation Console
</Heading>
<Text fontSize="small-medium" color="gray-300" textAlign="center">
Deploy our autogenerated Cloudformation template to the AWS account that you are
onboarding, to generate the necessary ReadOnly IAM Roles. After deployment please
continue with setup completion.
{initialValues.integrationId && (
<Box as="b" mt={3} display="block">
Make sure you select Update and then Replace current template
</Box>
)}
</Text>
<LinkButton external to={cfnConsoleLink} variantColor="teal">
Launch Console
</LinkButton>
</Flex>
</Card>
<Card variant="dark" p={6}>
<Flex spacing={6} px={80} mx="auto" mb={6} justify="center">
{!initialValues.integrationId && (
<Card variant="dark" p={6}>
<Flex direction="column" align="center" spacing={4}>
<Img src={lightningIllustration} alt="Lightning" nativeWidth={40} nativeHeight={40} />
<Heading as="h4" size="x-small">
Using Cloudformation Console
</Heading>
<Text fontSize="small-medium" color="gray-300" textAlign="center">
Deploy our autogenerated Cloudformation template to the AWS account that you are
onboarding, to generate the necessary ReadOnly IAM Roles. After deployment please
continue with setup completion.
</Text>
<LinkButton
external
loading={loading}
disabled={!!error || loading}
to={cfnConsoleLink}
variantColor="teal"
>
Launch Console
</LinkButton>
</Flex>
</Card>
)}
<Card variant="dark" p={6} maxWidth="50%">
<Flex direction="column" align="center" spacing={4}>
<Img src={cogsIllustration} alt="Cogssn" nativeWidth={40} nativeHeight={40} />
<Heading as="h4" size="x-small">
Using the AWS CLI
</Heading>
<Text fontSize="small-medium" color="gray-300" textAlign="center">
Download the autogenerated Cloudformation template and deploy it to the AWS account
that you are onboarding via the given CLI/SDK. After deployment please continue with
setup completion.
{initialValues.integrationId && (
<Box as="b" mt={3} display="block">
Make sure you update the template of the existing stack
</Box>
{initialValues.integrationId ? (
<React.Fragment>
Download the updated CloudFormation template and update the CloudFormation stack
created in the bucket&apos;s region during the onboarding process for this source.
<Box as="b" mt={3} display="block">
The default stack name would be: {initialValues.initialStackName}
</Box>
</React.Fragment>
) : (
'Download the autogenerated Cloudformation template and deploy it to the AWS account that you are onboarding via the given CLI/SDK. After deployment please continue with setup completion.'
)}
</Text>
<Button
icon="download"
variantColor="violet"
loading={loading}
disabled={loading}
disabled={!!error || loading}
onClick={() => downloadData(body, `${stackName}.yaml`)}
>
Get template file
</Button>
</Flex>
</Card>
</SimpleGrid>
</Flex>
<WizardPanel.Actions>
<WizardPanel.ActionPrev />
<Flex spacing={4} direction="column" align="center">
<Text fontSize="small">Already have your IAM roles setup?</Text>
<Text fontSize="small">
{initialValues.integrationId
? 'Already have your IAM roles setup?'
: 'After updating the stack, click below to proceed'}
</Text>
<Button variant="outline" variantColor="navyblue" onClick={goToNextStep}>
Continue
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ import {
waitMs,
buildListAvailableLogTypesResponse,
buildAddS3LogIntegrationInput,
buildIntegrationTemplate,
} from 'test-utils';
import { EventEnum, SrcEnum, trackError, TrackErrorEnum, trackEvent } from 'Helpers/analytics';
import { LOG_ONBOARDING_SNS_DOC_URL } from 'Source/constants';
import { mockListAvailableLogTypes } from 'Source/graphql/queries';
import { mockGetLogCfnTemplate } from 'Components/wizards/S3LogSourceWizard';
import { pantherConfig } from 'Source/config';
import { mockAddS3LogSource } from './graphql/addS3LogSource.generated';
import CreateS3LogSource from './CreateS3LogSource';

Expand All @@ -50,6 +53,21 @@ describe('CreateS3LogSource', () => {
listAvailableLogTypes: logTypesResponse,
},
}),
mockGetLogCfnTemplate({
variables: {
input: {
awsAccountId: pantherConfig.AWS_ACCOUNT_ID,
integrationLabel: logSource.integrationLabel,
s3Bucket: logSource.s3Bucket,
logTypes: logSource.logTypes,
kmsKey: null,
s3Prefix: null,
},
},
data: {
getS3LogIntegrationTemplate: buildIntegrationTemplate(),
},
}),
mockAddS3LogSource({
variables: {
input: buildAddS3LogIntegrationInput({
Expand Down Expand Up @@ -84,11 +102,13 @@ describe('CreateS3LogSource', () => {
await waitMs(50);
fireEvent.click(getByText('Continue Setup'));

// Initially we expect a disabled button while the template is being fetched ...
// Initially we expect 2 disabled buttons while the template is being fetched ...
expect(getByText('Get template file')).toHaveAttribute('disabled');
expect(getByText('Launch Console')).toHaveAttribute('aria-disabled', 'true');

// ... replaced by an active button as soon as it's fetched
await waitFor(() => expect(getByText('Get template file')).not.toHaveAttribute('disabled'));
expect(getByText('Launch Console')).toHaveAttribute('aria-disabled', 'false');

// We move on to the final screen
fireEvent.click(getByText('Continue'));
Expand Down
33 changes: 27 additions & 6 deletions web/src/pages/EditS3LogSource/ΕditS3LogSource.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ import {
waitMs,
buildListAvailableLogTypesResponse,
buildUpdateS3LogIntegrationInput,
buildIntegrationTemplate,
} from 'test-utils';
import { Route } from 'react-router';
import urls from 'Source/urls';
import { mockListAvailableLogTypes } from 'Source/graphql/queries';
import { EventEnum, SrcEnum, trackEvent } from 'Helpers/analytics';
import { mockGetLogCfnTemplate } from 'Components/wizards/S3LogSourceWizard';
import { pantherConfig } from 'Source/config';
import EditS3LogSource from './EditS3LogSource';
import { mockGetS3LogSource } from './graphql/getS3LogSource.generated';
import { mockUpdateS3LogSource } from './graphql/updateS3LogSource.generated';
Expand All @@ -42,7 +45,7 @@ describe('EditS3LogSource', () => {
const logSource = buildS3LogIntegration({
awsAccountId: '123123123123',
logTypes: logTypesResponse.logTypes,
kmsKey: '',
kmsKey: null,
});

const updatedLogSource = buildS3LogIntegration({ ...logSource, integrationLabel: 'new-value' });
Expand All @@ -61,14 +64,29 @@ describe('EditS3LogSource', () => {
listAvailableLogTypes: logTypesResponse,
},
}),
mockGetLogCfnTemplate({
variables: {
input: {
awsAccountId: pantherConfig.AWS_ACCOUNT_ID,
integrationLabel: updatedLogSource.integrationLabel,
s3Bucket: updatedLogSource.s3Bucket,
logTypes: updatedLogSource.logTypes,
kmsKey: updatedLogSource.kmsKey,
s3Prefix: updatedLogSource.s3Prefix,
},
},
data: {
getS3LogIntegrationTemplate: buildIntegrationTemplate(),
},
}),
mockUpdateS3LogSource({
variables: {
input: buildUpdateS3LogIntegrationInput({
integrationId: logSource.integrationId,
integrationId: updatedLogSource.integrationId,
integrationLabel: updatedLogSource.integrationLabel,
s3Bucket: logSource.s3Bucket,
logTypes: logSource.logTypes,
s3Prefix: logSource.s3Prefix,
s3Bucket: updatedLogSource.s3Bucket,
logTypes: updatedLogSource.logTypes,
s3Prefix: updatedLogSource.s3Prefix,
kmsKey: null,
}),
},
Expand All @@ -77,7 +95,7 @@ describe('EditS3LogSource', () => {
},
}),
];
const { getByText, getByLabelText, getByAltText, findByText } = render(
const { getByText, getByLabelText, getByAltText, findByText, queryByText } = render(
<Route path={urls.logAnalysis.sources.edit(':id', ':type')}>
<EditS3LogSource />
</Route>,
Expand Down Expand Up @@ -106,6 +124,9 @@ describe('EditS3LogSource', () => {
// ... replaced by an active button as soon as it's fetched
await waitFor(() => expect(getByText('Get template file')).not.toHaveAttribute('disabled'));

// We expect not to display a link button AWS Console for editing
expect(queryByText('Launch Console')).not.toBeInTheDocument();

// We move on to the final screen
fireEvent.click(getByText('Continue'));

Expand Down

0 comments on commit 5e22cc0

Please sign in to comment.