Launch button in stable environments

This moves Beta only features to stable environment:
- Sharing Images through Sources
- Launch button

This tries to avoid any refactoring, just moving components from Beta to stable with minimal changes.
This commit is contained in:
Ondrej Ezr 2023-08-15 16:40:53 +02:00 committed by Lucas Garfield
parent e5575d7a2d
commit 2a8e5a10df
12 changed files with 523 additions and 782 deletions

View file

@ -7,14 +7,12 @@ import { useNavigate, useParams } from 'react-router-dom';
import ImageCreator from './ImageCreator';
import {
awsTargetStable,
awsTargetBeta,
awsTarget,
fileSystemConfiguration,
googleCloudTarger,
googleCloudTarget,
imageName,
imageOutput,
msAzureTargetStable,
msAzureTargetBeta,
msAzureTarget,
packages,
packagesContentSources,
registration,
@ -516,8 +514,6 @@ const CreateImageWizard = () => {
const { isBeta, isProd } = useGetEnvironment();
const awsTarget = isBeta() ? awsTargetBeta : awsTargetStable;
const msAzureTarget = isBeta() ? msAzureTargetBeta : msAzureTargetStable;
let initialState = requestToState(
composeRequest,
distroInfo,
@ -622,7 +618,7 @@ const CreateImageWizard = () => {
fields: [
imageOutput,
awsTarget,
googleCloudTarger,
googleCloudTarget,
msAzureTarget,
registration,
packages,

View file

@ -1,187 +0,0 @@
import React from 'react';
import componentTypes from '@data-driven-forms/react-form-renderer/component-types';
import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types';
import {
Button,
HelperText,
HelperTextItem,
Title,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import nextStepMapper from './imageOutputStepMapper';
import StepTemplate from './stepTemplate';
import { DEFAULT_AWS_REGION } from '../../../constants';
import CustomButtons from '../formComponents/CustomButtons';
const SourcesButton = () => {
return (
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={'settings/sources'}
>
Create and manage sources here
</Button>
);
};
export default {
StepTemplate,
id: 'wizard-target-aws',
title: 'Amazon Web Services',
customTitle: (
<Title headingLevel="h1" size="xl">
Target environment - Amazon Web Services
</Title>
),
name: 'aws-target-env',
substepOf: 'Target environment',
nextStep: ({ values }) => nextStepMapper(values, { skipAws: true }),
buttons: CustomButtons,
fields: [
{
component: componentTypes.PLAIN_TEXT,
name: 'plain-text-component',
label: (
<p>
Your image will be uploaded to AWS and shared with the account you
provide below.
</p>
),
},
{
component: componentTypes.PLAIN_TEXT,
name: 'plain-text-component',
label: (
<p>
<b>The shared image will expire within 14 days.</b> To permanently
access the image, copy the image, which will be shared to your account
by Red Hat, to your own AWS account.
</p>
),
},
{
component: componentTypes.RADIO,
label: 'Share method:',
name: 'aws-target-type',
initialValue: 'aws-target-type-source',
autoFocus: true,
options: [
{
label: 'Use an account configured from Sources.',
description:
'Use a configured source to launch environments directly from the console.',
value: 'aws-target-type-source',
'data-testid': 'aws-radio-source',
autoFocus: true,
},
{
label: 'Manually enter an account ID.',
value: 'aws-target-type-account-id',
'data-testid': 'aws-radio-account-id',
className: 'pf-u-mt-sm',
},
],
},
{
component: 'aws-sources-select',
name: 'aws-sources-select',
className: 'pf-u-max-width',
label: 'Source Name',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'aws-target-type',
is: 'aws-target-type-source',
},
},
{
component: componentTypes.PLAIN_TEXT,
name: 'aws-sources-select-description',
label: <SourcesButton />,
condition: {
when: 'aws-target-type',
is: 'aws-target-type-source',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'aws-account-id',
className: 'pf-u-w-25',
'data-testid': 'aws-account-id',
type: 'text',
label: 'AWS account ID',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
{
type: validatorTypes.EXACT_LENGTH,
threshold: 12,
},
],
condition: {
when: 'aws-target-type',
is: 'aws-target-type-account-id',
},
},
{
name: 'gallery-layout',
component: 'gallery-layout',
minWidths: { default: '12.5rem' },
maxWidths: { default: '12.5rem' },
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'aws-default-region',
value: DEFAULT_AWS_REGION,
'data-testid': 'aws-default-region',
type: 'text',
label: 'Default Region',
isReadOnly: true,
isRequired: true,
helperText: (
<HelperText>
<HelperTextItem component="div" variant="indeterminate">
Images are built in the default region but can be copied to
other regions later.
</HelperTextItem>
</HelperText>
),
},
{
component: componentTypes.TEXT_FIELD,
name: 'aws-associated-account-id',
'data-testid': 'aws-associated-account-id',
type: 'text',
label: 'Associated Account ID',
isReadOnly: true,
isRequired: true,
helperText: (
<HelperText>
<HelperTextItem component="div" variant="indeterminate">
This is the account associated with the source.
</HelperTextItem>
</HelperText>
),
condition: {
when: 'aws-target-type',
is: 'aws-target-type-source',
},
},
],
},
],
};

View file

@ -2,7 +2,13 @@ import React from 'react';
import componentTypes from '@data-driven-forms/react-form-renderer/component-types';
import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types';
import { HelperText, HelperTextItem, Title } from '@patternfly/react-core';
import {
Button,
HelperText,
HelperTextItem,
Title,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import nextStepMapper from './imageOutputStepMapper';
import StepTemplate from './stepTemplate';
@ -10,6 +16,22 @@ import StepTemplate from './stepTemplate';
import { DEFAULT_AWS_REGION } from '../../../constants';
import CustomButtons from '../formComponents/CustomButtons';
const SourcesButton = () => {
return (
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={'settings/sources'}
>
Create and manage sources here
</Button>
);
};
export default {
StepTemplate,
id: 'wizard-target-aws',
@ -45,6 +67,54 @@ export default {
</p>
),
},
{
component: componentTypes.RADIO,
label: 'Share method:',
name: 'aws-target-type',
initialValue: 'aws-target-type-source',
autoFocus: true,
options: [
{
label: 'Use an account configured from Sources.',
description:
'Use a configured source to launch environments directly from the console.',
value: 'aws-target-type-source',
'data-testid': 'aws-radio-source',
autoFocus: true,
},
{
label: 'Manually enter an account ID.',
value: 'aws-target-type-account-id',
'data-testid': 'aws-radio-account-id',
className: 'pf-u-mt-sm',
},
],
},
{
component: 'aws-sources-select',
name: 'aws-sources-select',
className: 'pf-u-max-width',
label: 'Source Name',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'aws-target-type',
is: 'aws-target-type-source',
},
},
{
component: componentTypes.PLAIN_TEXT,
name: 'aws-sources-select-description',
label: <SourcesButton />,
condition: {
when: 'aws-target-type',
is: 'aws-target-type-source',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'aws-account-id',
@ -53,7 +123,6 @@ export default {
type: 'text',
label: 'AWS account ID',
isRequired: true,
autoFocus: true,
validate: [
{
type: validatorTypes.REQUIRED,
@ -63,29 +132,56 @@ export default {
threshold: 12,
},
],
condition: {
when: 'aws-target-type',
is: 'aws-target-type-account-id',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'aws-default-region',
className: 'pf-u-w-25',
'data-testid': 'aws-default-region',
type: 'text',
label: 'Default Region',
value: DEFAULT_AWS_REGION,
isReadOnly: true,
isRequired: true,
helperText: (
<HelperText>
<HelperTextItem
component="div"
variant="indeterminate"
className="pf-u-w-25"
>
Images are built in the default region but can be copied to other
regions later.
</HelperTextItem>
</HelperText>
),
name: 'gallery-layout',
component: 'gallery-layout',
minWidths: { default: '12.5rem' },
maxWidths: { default: '12.5rem' },
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'aws-default-region',
value: DEFAULT_AWS_REGION,
'data-testid': 'aws-default-region',
type: 'text',
label: 'Default Region',
isReadOnly: true,
isRequired: true,
helperText: (
<HelperText>
<HelperTextItem component="div" variant="indeterminate">
Images are built in the default region but can be copied to
other regions later.
</HelperTextItem>
</HelperText>
),
},
{
component: componentTypes.TEXT_FIELD,
name: 'aws-associated-account-id',
'data-testid': 'aws-associated-account-id',
type: 'text',
label: 'Associated Account ID',
isReadOnly: true,
isRequired: true,
helperText: (
<HelperText>
<HelperTextItem component="div" variant="indeterminate">
This is the account associated with the source.
</HelperTextItem>
</HelperText>
),
condition: {
when: 'aws-target-type',
is: 'aws-target-type-source',
},
},
],
},
],
};

View file

@ -1,8 +1,6 @@
export { default as awsTargetStable } from './aws';
export { default as awsTargetBeta } from './aws.beta';
export { default as googleCloudTarger } from './googleCloud';
export { default as msAzureTargetStable } from './msAzure';
export { default as msAzureTargetBeta } from './msAzure.beta';
export { default as awsTarget } from './aws';
export { default as googleCloudTarget } from './googleCloud';
export { default as msAzureTarget } from './msAzure';
export { default as packages } from './packages';
export { default as packagesContentSources } from './packagesContentSources';
export { default as registration } from './registration';

View file

@ -1,262 +0,0 @@
import React from 'react';
import componentTypes from '@data-driven-forms/react-form-renderer/component-types';
import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types';
import { Button, Text, TextContent, Title } from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import nextStepMapper from './imageOutputStepMapper';
import StepTemplate from './stepTemplate';
import CustomButtons from '../formComponents/CustomButtons';
const SourcesButton = () => {
return (
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={'settings/sources'}
>
Create and manage sources here
</Button>
);
};
export default {
StepTemplate,
id: 'wizard-target-msazure',
title: 'Microsoft Azure',
customTitle: (
<Title headingLevel="h1" size="xl">
Target environment - Microsoft Azure
</Title>
),
name: 'ms-azure-target-env',
substepOf: 'Target environment',
nextStep: ({ values }) =>
nextStepMapper(values, {
skipAws: true,
skipGoogle: true,
skipAzure: true,
}),
buttons: CustomButtons,
fields: [
{
component: componentTypes.PLAIN_TEXT,
name: 'azure-description',
label: (
<TextContent>
<Text>
Upon build, Image Builder sends the image to the selected authorized
Azure account. The image will be uploaded to the resource group in
the subscription you specify.
</Text>
<Text>
To authorize Image Builder to push images to Microsoft Azure, the
account owner must configure Image Builder as an authorized
application for a specific tenant ID and give it the role of
&quot;Contributor&quot; for the resource group you want to upload
to. This applies even when defining target by Source selection.
<br />
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow"
>
Learn more about OAuth 2.0
</Button>
</Text>
</TextContent>
),
},
{
component: componentTypes.RADIO,
label: 'Share method:',
name: 'azure-type',
initialValue: 'azure-type-source',
autoFocus: true,
options: [
{
label: 'Use an account configured from Sources.',
description:
'Use a configured source to launch environments directly from the console.',
value: 'azure-type-source',
'data-testid': 'azure-radio-source',
autoFocus: true,
},
{
label: 'Manually enter the account information.',
value: 'azure-type-manual',
'data-testid': 'azure-radio-manual',
className: 'pf-u-mt-sm',
},
],
},
{
component: 'azure-sources-select',
name: 'azure-sources-select',
className: 'pf-u-max-width',
label: 'Source Name',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
component: componentTypes.PLAIN_TEXT,
name: 'azure-sources-select-description',
label: <SourcesButton />,
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
name: 'gallery-layout',
component: 'gallery-layout',
minWidths: { default: '12.5rem' },
maxWidths: { default: '12.5rem' },
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'azure-tenant-id',
'data-testid': 'azure-tenant-id-source',
type: 'text',
label: 'Azure Tenant GUID',
isRequired: true,
isReadOnly: true,
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-subscription-id',
'data-testid': 'azure-subscription-id-source',
type: 'text',
label: 'Subscription ID',
isRequired: true,
isReadOnly: true,
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
],
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-tenant-id',
className: 'pf-u-w-50',
'data-testid': 'azure-tenant-id-manual',
type: 'text',
label: 'Azure Tenant GUID',
isRequired: true,
autoFocus: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
{
type: validatorTypes.PATTERN,
pattern:
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
message: 'Please enter a valid tenant ID',
},
],
condition: {
when: 'azure-type',
is: 'azure-type-manual',
},
},
{
component: 'azure-auth-button',
name: 'azure-auth-button',
'data-testid': 'azure-auth-button',
required: true,
isRequired: true,
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-subscription-id',
className: 'pf-u-w-50',
'data-testid': 'azure-subscription-id-manual',
type: 'text',
label: 'Subscription ID',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
{
type: validatorTypes.PATTERN,
pattern:
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
message: 'Please enter a valid subscription ID',
},
],
condition: {
when: 'azure-type',
is: 'azure-type-manual',
},
},
{
component: 'azure-resource-groups',
name: 'azure-resource-group',
className: 'pf-u-max-width',
'data-testid': 'azure-resource-group-select',
label: 'Resource group',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-resource-group',
className: 'pf-u-w-50',
'data-testid': 'azure-resource-group-manual',
type: 'text',
label: 'Resource group',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
{
type: validatorTypes.PATTERN,
pattern: /^[-\w._()]+[-\w_()]$/,
message:
'Resource group names only allow alphanumeric characters, ' +
'periods, underscores, hyphens, and parenthesis and cannot end in a period',
},
],
condition: {
when: 'azure-type',
is: 'azure-type-manual',
},
},
// TODO check oauth2 thing too here?
],
};

View file

@ -10,6 +10,22 @@ import StepTemplate from './stepTemplate';
import CustomButtons from '../formComponents/CustomButtons';
const SourcesButton = () => {
return (
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={'settings/sources'}
>
Create and manage sources here
</Button>
);
};
export default {
StepTemplate,
id: 'wizard-target-msazure',
@ -43,7 +59,8 @@ export default {
To authorize Image Builder to push images to Microsoft Azure, the
account owner must configure Image Builder as an authorized
application for a specific tenant ID and give it the role of
&quot;Contributor&quot; to at least one resource group.
&quot;Contributor&quot; for the resource group you want to upload
to. This applies even when defining target by Source selection.
<br />
<Button
component="a"
@ -60,14 +77,95 @@ export default {
</TextContent>
),
},
{
component: componentTypes.RADIO,
label: 'Share method:',
name: 'azure-type',
initialValue: 'azure-type-source',
autoFocus: true,
options: [
{
label: 'Use an account configured from Sources.',
description:
'Use a configured source to launch environments directly from the console.',
value: 'azure-type-source',
'data-testid': 'azure-radio-source',
autoFocus: true,
},
{
label: 'Manually enter the account information.',
value: 'azure-type-manual',
'data-testid': 'azure-radio-manual',
className: 'pf-u-mt-sm',
},
],
},
{
component: 'azure-sources-select',
name: 'azure-sources-select',
className: 'pf-u-max-width',
label: 'Source Name',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
component: componentTypes.PLAIN_TEXT,
name: 'azure-sources-select-description',
label: <SourcesButton />,
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
name: 'gallery-layout',
component: 'gallery-layout',
minWidths: { default: '12.5rem' },
maxWidths: { default: '12.5rem' },
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'azure-tenant-id',
'data-testid': 'azure-tenant-id-source',
type: 'text',
label: 'Azure Tenant GUID',
isRequired: true,
isReadOnly: true,
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-subscription-id',
'data-testid': 'azure-subscription-id-source',
type: 'text',
label: 'Subscription ID',
isRequired: true,
isReadOnly: true,
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
],
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-tenant-id',
className: 'pf-u-w-50',
'data-testid': 'azure-tenant-id',
'data-testid': 'azure-tenant-id-manual',
type: 'text',
label: 'Azure Tenant GUID',
required: true,
isRequired: true,
autoFocus: true,
validate: [
@ -81,6 +179,10 @@ export default {
message: 'Please enter a valid tenant ID',
},
],
condition: {
when: 'azure-type',
is: 'azure-type-manual',
},
},
{
component: 'azure-auth-button',
@ -93,7 +195,7 @@ export default {
component: componentTypes.TEXT_FIELD,
name: 'azure-subscription-id',
className: 'pf-u-w-50',
'data-testid': 'azure-subscription-id',
'data-testid': 'azure-subscription-id-manual',
type: 'text',
label: 'Subscription ID',
isRequired: true,
@ -108,12 +210,33 @@ export default {
message: 'Please enter a valid subscription ID',
},
],
condition: {
when: 'azure-type',
is: 'azure-type-manual',
},
},
{
component: 'azure-resource-groups',
name: 'azure-resource-group',
className: 'pf-u-max-width',
'data-testid': 'azure-resource-group-select',
label: 'Resource group',
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'azure-type',
is: 'azure-type-source',
},
},
{
component: componentTypes.TEXT_FIELD,
name: 'azure-resource-group',
className: 'pf-u-w-50',
'data-testid': 'azure-resource-group',
'data-testid': 'azure-resource-group-manual',
type: 'text',
label: 'Resource group',
isRequired: true,
@ -129,6 +252,10 @@ export default {
'periods, underscores, hyphens, and parenthesis and cannot end in a period',
},
],
condition: {
when: 'azure-type',
is: 'azure-type-manual',
},
},
// TODO check oauth2 thing too here?
],

View file

@ -11,7 +11,6 @@ import ImageLinkDirect from './ImageLinkDirect';
import { MODAL_ANCHOR } from '../../constants';
import { selectImageById } from '../../store/composesSlice';
import { useGetEnvironment } from '../../Utilities/useGetEnvironment';
const getImageProvider = ({ imageType }) => {
switch (imageType) {
@ -97,12 +96,10 @@ const ImageLink = ({ imageId, isExpired, isInClonesTable }) => {
const image = useSelector((state) => selectImageById(state, imageId));
const uploadStatus = image.uploadStatus;
const { initialized: chromeInitialized, getEnvironment } = useChrome();
const { isBeta } = useGetEnvironment();
const azureFeatureFlag = useFlag('provisioning.azure');
const gcpFeatureFlag = useFlag('provisioning.gcp');
const scalprum = useScalprum();
const hasProvisioning =
chromeInitialized && scalprum.config?.provisioning && isBeta();
const hasProvisioning = chromeInitialized && scalprum.config?.provisioning;
if (!uploadStatus || image.status !== 'success') return null;

View file

@ -0,0 +1,3 @@
import actions from 'actions';
export default actions;

View file

@ -38,7 +38,7 @@ jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
};
},
},
isBeta: () => true,
isBeta: () => false,
isProd: () => true,
getEnvironment: () => 'prod',
}),

View file

@ -1,15 +1,17 @@
import React from 'react';
import '@testing-library/jest-dom';
import { screen, waitFor } from '@testing-library/react';
import { act, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CreateImageWizard from '../../../Components/CreateImageWizard/CreateImageWizard';
import ShareImageModal from '../../../Components/ShareImageModal/ShareImageModal';
import {
clickBack,
clickNext,
getNextButton,
renderCustomRoutesWithReduxRouter,
verifyCancelButton,
} from '../../testUtils';
const routes = [
@ -39,12 +41,14 @@ jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
};
},
},
isBeta: () => true,
isBeta: () => false,
isProd: () => true,
getEnvironment: () => 'prod',
}),
}));
let router = undefined;
describe('Step Upload to Azure', () => {
const getSourceDropdown = async () => {
const sourceDropdown = await screen.findByRole('textbox', {
@ -63,12 +67,13 @@ describe('Step Upload to Azure', () => {
afterEach(() => {
jest.clearAllMocks();
router = undefined;
});
const user = userEvent.setup();
const setUp = async () => {
renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select aws as upload destination
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
// select Azure as upload destination
const azureTile = screen.getByTestId('upload-azure');
azureTile.click();
@ -79,6 +84,50 @@ describe('Step Upload to Azure', () => {
);
};
test('clicking Next loads Registration', async () => {
await setUp();
await user.click(screen.getByTestId('azure-radio-manual'));
// Randomly generated GUID
await user.type(
screen.getByTestId('azure-tenant-id-manual'),
'b8f86d22-4371-46ce-95e7-65c415f3b1e2'
);
await user.type(
screen.getByTestId('azure-subscription-id-manual'),
'60631143-a7dc-4d15-988b-ba83f3c99711'
);
await user.type(
screen.getByTestId('azure-resource-group-manual'),
'testResourceGroup'
);
await act(async () => {
await clickNext();
});
await screen.findByRole('textbox', {
name: 'Select activation key',
});
await screen.findByText(
'Automatically register and enable advanced capabilities'
);
});
test('clicking Back loads Release', async () => {
await setUp();
await clickBack();
screen.getByTestId('upload-azure');
});
test('clicking Cancel loads landing page', async () => {
await setUp();
await verifyCancelButton(router);
});
test('azure step basics works', async () => {
await setUp();
@ -90,18 +139,18 @@ describe('Step Upload to Azure', () => {
expect(await getNextButton()).toHaveClass('pf-m-disabled');
await user.type(
screen.getByTestId('azure-tenant-id-manual'),
'c983c2cd-94d7-44e1-9c6e-9cfa3a40995f'
);
await user.type(
screen.getByTestId('azure-subscription-id-manual'),
'f8f200aa-6234-4bfb-86c2-163d33dffc0c'
);
await user.type(
screen.getByTestId('azure-resource-group-manual'),
'testGroup'
);
const tenantId = screen.getByTestId('azure-tenant-id-manual');
expect(tenantId).toHaveValue('');
expect(tenantId).toBeEnabled();
await user.type(tenantId, 'c983c2cd-94d7-44e1-9c6e-9cfa3a40995f');
const subscription = screen.getByTestId('azure-subscription-id-manual');
expect(subscription).toHaveValue('');
expect(subscription).toBeEnabled();
await user.type(subscription, 'f8f200aa-6234-4bfb-86c2-163d33dffc0c');
const resourceGroup = screen.getByTestId('azure-resource-group-manual');
expect(resourceGroup).toHaveValue('');
expect(resourceGroup).toBeEnabled();
await user.type(resourceGroup, 'testGroup');
expect(await getNextButton()).not.toHaveClass('pf-m-disabled');

View file

@ -10,20 +10,17 @@ import {
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import api from '../../../api.js';
import CreateImageWizard from '../../../Components/CreateImageWizard/CreateImageWizard';
import ShareImageModal from '../../../Components/ShareImageModal/ShareImageModal';
import { RHEL_8, RHEL_9, PROVISIONING_API } from '../../../constants.js';
import { RHEL_8 } from '../../../constants.js';
import { mockComposesEmpty } from '../../fixtures/composes';
import { customizations } from '../../fixtures/customizations';
import { mockPkgResultAlphaContentSources } from '../../fixtures/packages';
import { server } from '../../mocks/server.js';
import {
clickBack,
clickNext,
getNextButton,
renderCustomRoutesWithReduxRouter,
} from '../../testUtils';
@ -117,168 +114,6 @@ describe('Create Image Wizard', () => {
});
});
describe('Step Upload to AWS', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router, store } = renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
const awsTile = await screen.findByTestId('upload-aws');
await act(async () => {
awsTile.click();
});
await clickNext();
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
'Target environment - Amazon Web Services'
);
};
test('component renders error state correctly', async () => {
await setUp();
server.use(
rest.get(`${PROVISIONING_API}/sources`, (_req, res, ctx) =>
res(ctx.status(500))
)
);
await screen.findByText(
/sources cannot be reached, try again later or enter an aws account id manually\./i
);
});
test('validation works', async () => {
await setUp();
// jsdom seems to render the next button differently than the browser. The
// next button is enabled briefly during the test. This does not occur in
// the browser. Using findByRole instead of getByRole to get the next
// button allows us to capture its 'final' state.
expect(await getNextButton()).toHaveClass('pf-m-disabled');
await user.click(
screen.getByRole('radio', { name: /manually enter an account id\./i })
);
expect(await getNextButton()).toHaveClass('pf-m-disabled');
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
expect(await getNextButton()).not.toHaveClass('pf-m-disabled');
screen
.getByRole('radio', { name: /use an account configured from sources\./i })
.click();
expect(await getNextButton()).toHaveClass('pf-m-disabled');
const sourceDropdown = screen.getByRole('textbox', {
name: /select source/i,
});
// Wait for isSuccess === true, dropdown is disabled while isSuccess === false
await waitFor(() => expect(sourceDropdown).toBeEnabled());
sourceDropdown.click();
const source = await screen.findByRole('option', {
name: /my_source/i,
});
source.click();
expect(await getNextButton()).not.toHaveClass('pf-m-disabled');
});
test('compose request share_with_sources field is correct', async () => {
await setUp();
const sourceDropdown = await screen.findByRole('textbox', {
name: /select source/i,
});
// Wait for isSuccess === true, dropdown is disabled while isSuccess === false
await waitFor(() => expect(sourceDropdown).toBeEnabled());
await act(async () => {
sourceDropdown.click();
});
const source = await screen.findByRole('option', {
name: /my_source/i,
});
await act(async () => {
source.click();
});
await act(async () => {
await clickNext();
});
// registration
await screen.findByRole('textbox', {
name: 'Select activation key',
});
const registerLaterRadio = screen.getByLabelText('Register later');
await act(async () => {
await user.click(registerLaterRadio);
});
// click through to review step
await act(async () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await clickNext();
});
const composeImage = jest
.spyOn(api, 'composeImage')
.mockImplementation((body) => {
expect(body).toEqual({
distribution: RHEL_9,
image_name: undefined,
customizations: {
packages: undefined,
},
image_requests: [
{
architecture: 'x86_64',
image_type: 'aws',
upload_request: {
type: 'aws',
options: {
share_with_sources: ['123'],
},
},
},
],
});
const id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5a';
return Promise.resolve({ id });
});
const create = screen.getByRole('button', { name: /Create/ });
await act(async () => {
create.click();
});
// API request sent to backend
expect(composeImage).toHaveBeenCalledTimes(1);
// returns back to the landing page
await waitFor(() =>
expect(router.state.location.pathname).toBe('/insights/image-builder')
);
expect(store.getState().composes.allIds).toEqual([
'edbae1c2-62bc-42c1-ae0c-3110ab718f5a',
]);
// set test timeout of 10 seconds
}, 10000);
});
describe('Step Packages', () => {
const user = userEvent.setup();
const setUp = async () => {

View file

@ -10,17 +10,19 @@ import {
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import api from '../../../api.js';
import CreateImageWizard from '../../../Components/CreateImageWizard/CreateImageWizard';
import ShareImageModal from '../../../Components/ShareImageModal/ShareImageModal';
import { RHEL_8 } from '../../../constants.js';
import { RHEL_8, RHEL_9, PROVISIONING_API } from '../../../constants.js';
import { mockComposesEmpty } from '../../fixtures/composes';
import {
mockPkgResultAlpha,
mockPkgResultAll,
mockPkgResultPartial,
} from '../../fixtures/packages';
import { server } from '../../mocks/server.js';
import {
clickBack,
clickNext,
@ -93,6 +95,23 @@ const searchForChosenPackages = async (searchbox, searchTerm) => {
}
};
const switchToAWSManual = () => {
const manualRadio = screen.getByRole('radio', {
name: /manually enter an account id\./i,
});
manualRadio.click();
return manualRadio;
};
const getSourceDropdown = async () => {
const sourceDropdown = screen.getByRole('textbox', {
name: /select source/i,
});
await waitFor(() => expect(sourceDropdown).toBeEnabled());
return sourceDropdown;
};
beforeAll(() => {
// scrollTo is not defined in jsdom
window.HTMLElement.prototype.scrollTo = function () {};
@ -141,6 +160,7 @@ describe('Step Image output', () => {
await clickNext();
switchToAWSManual();
screen.getByText('AWS account ID');
});
@ -257,7 +277,11 @@ describe('Step Image output', () => {
describe('Step Upload to AWS', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router, store } = renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
const awsTile = screen.getByTestId('upload-aws');
@ -271,8 +295,9 @@ describe('Step Upload to AWS', () => {
};
test('clicking Next loads Registration', async () => {
setUp();
await setUp();
switchToAWSManual();
await user.type(
await screen.findByTestId('aws-account-id'),
'012345678901'
@ -304,14 +329,136 @@ describe('Step Upload to AWS', () => {
await verifyCancelButton(router);
});
test('the aws account id fieldis shown and required', async () => {
test('component renders error state correctly', async () => {
await setUp();
server.use(
rest.get(`${PROVISIONING_API}/sources`, (_req, res, ctx) =>
res(ctx.status(500))
)
);
await screen.findByText(
/sources cannot be reached, try again later or enter an aws account id manually\./i
);
});
test('validation works', async () => {
await setUp();
const accessKeyId = screen.getByTestId('aws-account-id');
expect(accessKeyId).toHaveValue('');
expect(accessKeyId).toBeEnabled();
// expect(accessKeyId).toBeRequired(); // DDf does not support required value
// jsdom seems to render the next button differently than the browser. The
// next button is enabled briefly during the test. This does not occur in
// the browser. Using findByRole instead of getByRole to get the next
// button allows us to capture its 'final' state.
expect(await getNextButton()).toHaveClass('pf-m-disabled');
await user.click(
screen.getByRole('radio', { name: /manually enter an account id\./i })
);
expect(await getNextButton()).toHaveClass('pf-m-disabled');
const awsAccId = screen.getByTestId('aws-account-id');
expect(awsAccId).toHaveValue('');
expect(awsAccId).toBeEnabled();
await user.type(awsAccId, '012345678901');
expect(await getNextButton()).not.toHaveClass('pf-m-disabled');
screen
.getByRole('radio', { name: /use an account configured from sources\./i })
.click();
expect(await getNextButton()).toHaveClass('pf-m-disabled');
const sourceDropdown = await getSourceDropdown();
sourceDropdown.click();
const source = await screen.findByRole('option', {
name: /my_source/i,
});
source.click();
expect(await getNextButton()).not.toHaveClass('pf-m-disabled');
});
test('compose request share_with_sources field is correct', async () => {
await setUp();
const sourceDropdown = await getSourceDropdown();
sourceDropdown.click();
const source = await screen.findByRole('option', {
name: /my_source/i,
});
await act(async () => {
source.click();
});
await act(async () => {
await clickNext();
});
// registration
await screen.findByRole('textbox', {
name: 'Select activation key',
});
const registerLaterRadio = screen.getByLabelText('Register later');
await act(async () => {
await user.click(registerLaterRadio);
});
// click through to review step
await act(async () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
});
const composeImage = jest
.spyOn(api, 'composeImage')
.mockImplementation((body) => {
expect(body).toEqual({
distribution: RHEL_9,
image_name: undefined,
customizations: {
packages: undefined,
},
image_requests: [
{
architecture: 'x86_64',
image_type: 'aws',
upload_request: {
type: 'aws',
options: {
share_with_sources: ['123'],
},
},
},
],
});
const id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5a';
return Promise.resolve({ id });
});
const create = screen.getByRole('button', { name: /Create/ });
await act(async () => {
create.click();
});
// API request sent to backend
expect(composeImage).toHaveBeenCalledTimes(1);
// returns back to the landing page
await waitFor(() =>
expect(router.state.location.pathname).toBe('/insights/image-builder')
);
expect(store.getState().composes.allIds).toEqual([
'edbae1c2-62bc-42c1-ae0c-3110ab718f5a',
]);
// set test timeout of 10 seconds
}, 10000);
});
describe('Step Upload to Google', () => {
@ -380,83 +527,6 @@ describe('Step Upload to Google', () => {
});
});
describe('Step Upload to Azure', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
// select aws as upload destination
const awsTile = screen.getByTestId('upload-azure');
await awsTile.click();
await clickNext();
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
'Target environment - Microsoft Azure'
);
};
test('clicking Next loads Registration', async () => {
await setUp();
// Randomly generated GUID
await user.type(
screen.getByTestId('azure-tenant-id'),
'b8f86d22-4371-46ce-95e7-65c415f3b1e2'
);
await user.type(
screen.getByTestId('azure-subscription-id'),
'60631143-a7dc-4d15-988b-ba83f3c99711'
);
await user.type(
screen.getByTestId('azure-resource-group'),
'testResourceGroup'
);
await act(async () => {
await clickNext();
});
await screen.findByRole('textbox', {
name: 'Select activation key',
});
await screen.findByText(
'Automatically register and enable advanced capabilities'
);
});
test('clicking Back loads Release', async () => {
await setUp();
await clickBack();
screen.getByTestId('upload-azure');
});
test('clicking Cancel loads landing page', async () => {
await setUp();
await verifyCancelButton(router);
});
test('the azure upload fields are shown and required', async () => {
await setUp();
const tenantId = screen.getByTestId('azure-tenant-id');
expect(tenantId).toHaveValue('');
expect(tenantId).toBeEnabled();
// expect(tenantId).toBeRequired(); // DDf does not support required value
const subscription = screen.getByTestId('azure-subscription-id');
expect(subscription).toHaveValue('');
expect(subscription).toBeEnabled();
// expect(subscription).toBeRequired(); // DDf does not support required value
const resourceGroup = screen.getByTestId('azure-resource-group');
expect(resourceGroup).toHaveValue('');
expect(resourceGroup).toBeEnabled();
// expect(resourceGroup).toBeRequired(); // DDf does not support required value
});
});
describe('Step Registration', () => {
const user = userEvent.setup();
const setUp = async () => {
@ -469,6 +539,9 @@ describe('Step Registration', () => {
await act(async () => {
await clickNext();
});
await user.click(
screen.getByRole('radio', { name: /manually enter an account id\./i })
);
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
await act(async () => {
await clickNext();
@ -497,6 +570,9 @@ describe('Step Registration', () => {
await clickBack();
await user.click(
screen.getByRole('radio', { name: /manually enter an account id\./i })
);
screen.getByText('AWS account ID');
});
@ -745,6 +821,7 @@ describe('Step File system configuration', () => {
});
// aws step
switchToAWSManual();
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
await act(async () => {
await clickNext();
@ -817,6 +894,7 @@ describe('Step Packages', () => {
});
// aws step
switchToAWSManual();
const aai = screen.getByTestId('aws-account-id');
await act(async () => {
await user.type(aai, '012345678901');
@ -1265,6 +1343,7 @@ describe('Step Details', () => {
});
// aws step
switchToAWSManual();
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
await act(async () => {
await clickNext();
@ -1337,6 +1416,7 @@ describe('Step Review', () => {
});
// aws step
switchToAWSManual();
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
await act(async () => {
await clickNext();
@ -1388,6 +1468,7 @@ describe('Step Review', () => {
});
// aws step
switchToAWSManual();
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
await act(async () => {
await clickNext();
@ -1507,6 +1588,9 @@ describe('Click through all steps', () => {
await act(async () => {
bn.click();
});
await user.click(
screen.getByRole('radio', { name: /manually enter an account id\./i })
);
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
const bn1 = screen.getByRole('button', { name: /Next/ });
await act(async () => {
@ -1519,17 +1603,18 @@ describe('Click through all steps', () => {
bn2.click();
});
await user.click(screen.getByTestId('azure-radio-manual'));
// Randomly generated GUID
await user.type(
screen.getByTestId('azure-tenant-id'),
screen.getByTestId('azure-tenant-id-manual'),
'b8f86d22-4371-46ce-95e7-65c415f3b1e2'
);
await user.type(
screen.getByTestId('azure-subscription-id'),
screen.getByTestId('azure-subscription-id-manual'),
'60631143-a7dc-4d15-988b-ba83f3c99711'
);
await user.type(
screen.getByTestId('azure-resource-group'),
screen.getByTestId('azure-resource-group-manual'),
'testResourceGroup'
);
const bn3 = screen.getByRole('button', { name: /Next/ });
@ -1994,9 +2079,14 @@ describe('Keyboard accessibility', () => {
});
// Target environment aws
const awsInput = screen.getByRole('textbox', { name: /aws account id/i });
expect(awsInput).toHaveFocus();
await user.type(awsInput, '012345678901');
expect(screen.getByTestId('aws-radio-source')).toHaveFocus();
const awsSourceDropdown = await getSourceDropdown();
awsSourceDropdown.click();
const awsSource = await screen.findByRole('option', {
name: /my_source/i,
});
awsSource.click();
await act(async () => {
await clickNext();
});
@ -2012,20 +2102,19 @@ describe('Keyboard accessibility', () => {
});
// Target environment azure
const tenantIDInput = screen.getByTestId('azure-tenant-id');
expect(tenantIDInput).toHaveFocus();
await user.type(
screen.getByTestId('azure-tenant-id'),
'b8f86d22-4371-46ce-95e7-65c415f3b1e2'
);
await user.type(
screen.getByTestId('azure-subscription-id'),
'60631143-a7dc-4d15-988b-ba83f3c99711'
);
await user.type(
screen.getByTestId('azure-resource-group'),
'testResourceGroup'
);
expect(screen.getByTestId('azure-radio-source')).toHaveFocus();
const azureSourceDropdown = await getSourceDropdown();
azureSourceDropdown.click();
const azureSource = await screen.findByRole('option', {
name: /azureSource1/i,
});
azureSource.click();
const resourceGroupDropdown = await screen.findByRole('textbox', {
name: /select resource group/i,
});
await user.click(resourceGroupDropdown);
await user.click(screen.getByLabelText('Resource group myResourceGroup1'));
await act(async () => {
await clickNext();
});