Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions e2e/specs/wizard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@
import { expect, test } from '@playwright/test';
import { visitAndLogin } from '../test-utils/login';

const CLUSTER_NAME = Math.random().toString(20).substring(8)
const CLUSTER_NAME = 'c' + Math.random().toString(20).substring(8)

test.describe('Given an endpoint where AWS ParallelCluster UI is deployed', () => {
test('a user should be able to login, navigate till the end of the cluster creation wizard, and perform a dry-run successfully', async ({ page }) => {
await visitAndLogin(page)

await page.getByRole('button', { name: 'Create cluster' }).first().click();

await expect(page.getByRole('heading', { name: 'Source' })).toBeVisible()
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('menuitem', { name: 'Use interface' }).click();

await expect(page.getByRole('heading', { name: 'Cluster', exact: true })).toBeVisible()
await page.getByPlaceholder('Enter your cluster name').fill(CLUSTER_NAME);
Expand Down
8 changes: 3 additions & 5 deletions e2e/specs/wizard.template.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { expect, FileChooser, test } from '@playwright/test';
import { visitAndLogin } from '../test-utils/login';

const TEMPLATE_PATH = './fixtures/wizard.template.yaml'
const CLUSTER_NAME = Math.random().toString(20).substring(8)
const CLUSTER_NAME = 'c' + Math.random().toString(20).substring(8)

test.describe('environment: @demo', () => {
test.describe('given a cluster configuration template created with single instance type', () => {
Expand All @@ -21,17 +21,15 @@ test.describe('environment: @demo', () => {
await visitAndLogin(page)

await page.getByRole('button', { name: 'Create cluster' }).first().click();

await expect(page.getByRole('heading', { name: 'Source' })).toBeVisible()

page.on("filechooser", (fileChooser: FileChooser) => {
fileChooser.setFiles([TEMPLATE_PATH]);
})
await page.getByRole('radio', { name: 'Existing template' }).click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('menuitem', { name: 'Upload a template' }).click();

await expect(page.getByRole('heading', { name: 'Cluster', exact: true })).toBeVisible()
await page.getByPlaceholder('Enter your cluster name').fill(CLUSTER_NAME);
await page.getByText(/vpc-.*/).waitFor({state: 'visible'})
await page.getByRole('button', { name: 'Next' }).click();

await expect(page.getByRole('heading', { name: 'Head node' })).toBeVisible()
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/old-pages/Clusters/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
hideDialog,
} from '../../components/DeleteDialog'
import {StopDialog, stopComputeFleet} from './StopDialog'
import {CreateButtonDropdown} from './CreateButtonDropdown/CreateButtonDropdown'
import {wizardShow} from '../Configure/Configure'
import {ButtonDropdown} from '@cloudscape-design/components'
import {CancelableEventHandler} from '@cloudscape-design/components/internal/events'
Expand Down Expand Up @@ -212,7 +213,7 @@ export default function Actions() {
}, [isSsmDisabled, isEditDisabled, isDeleteDisabled, t])

return (
<div style={{marginLeft: '20px'}}>
<>
<DeleteDialog
id="deleteCluster"
header={t('cluster.list.dialogs.delete.title')}
Expand Down Expand Up @@ -254,10 +255,8 @@ export default function Actions() {
{t('cluster.list.actionsLabel')}
</ButtonDropdown>

<Button onClick={configure} variant="primary">
{t('cluster.list.actions.create')}
</Button>
<CreateButtonDropdown openWizard={wizardShow} />
</SpaceBetween>
</div>
</>
)
}
17 changes: 0 additions & 17 deletions frontend/src/old-pages/Clusters/__tests__/Clusters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,6 @@ describe('given a component to show the clusters list', () => {
expect(mockNavigate).toHaveBeenCalledWith('/clusters/test-cluster')
})
})

describe('when the user clicks on "Create Cluster" button', () => {
it('should redirect to configure', async () => {
const output = await waitFor(() =>
render(
<MockProviders>
<Clusters />
</MockProviders>,
),
)

await userEvent.click(
output.getByRole('button', {name: 'Create cluster'}),
)
expect(mockNavigate).toHaveBeenCalledWith('/configure')
})
})
})

describe('when there are no clusters available', () => {
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/old-pages/Configure/Cluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,11 @@ function clusterValidate() {
clearState([...errorsPath, 'multiUser'])
}

const clusterNameValid = validateClusterNameAndSetErrors()
if (!clusterNameValid) {
valid = false
if (!editing) {
const clusterNameValid = validateClusterNameAndSetErrors()
if (!clusterNameValid) {
valid = false
}
}

const accountingValid = slurmAccountingValidateAndSetErrors()
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/old-pages/Configure/Cluster/ClusterNameField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ const clusterNameErrorPath = [
'source',
'clusterName',
]
const editingPath = ['app', 'wizard', 'editing']

export function ClusterNameField() {
const {t} = useTranslation()
const clusterName = useState(clusterNamePath) || ''
const clusterNameError = useState(clusterNameErrorPath)
const editing = !!useState(editingPath)

const onChange: NonCancelableEventHandler<InputProps.ChangeDetail> =
useCallback(({detail}) => {
Expand All @@ -41,6 +43,7 @@ export function ClusterNameField() {
errorText={clusterNameError}
>
<Input
disabled={editing}
onChange={onChange}
value={clusterName}
placeholder={t('wizard.cluster.clusterName.placeholder')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
// with the License. A copy of the License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
// limitations under the License.

import {Store} from '@reduxjs/toolkit'
import {fireEvent, render, RenderResult} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import i18n from 'i18next'
import {mock} from 'jest-mock-extended'
import {I18nextProvider, initReactI18next} from 'react-i18next'
import {Provider} from 'react-redux'
import {setState as mockSetState} from '../../../../store'
import {ClusterNameField} from '../ClusterNameField'

jest.mock('../../../../store', () => {
const originalModule = jest.requireActual('../../../../store')

return {
__esModule: true, // Use it when dealing with esModules
...originalModule,
setState: jest.fn(),
}
})

i18n.use(initReactI18next).init({
resources: {},
lng: 'en',
})

const mockStore = mock<Store>()
const MockProviders = (props: any) => (
<Provider store={mockStore}>
<I18nextProvider i18n={i18n}>{props.children}</I18nextProvider>
</Provider>
)

describe('given a component to set the ClusterName', () => {
let screen: RenderResult

beforeEach(() => {
;(mockSetState as jest.Mock).mockClear()
})

describe('when user fills in the cluster name', () => {
beforeEach(() => {
screen = render(
<MockProviders>
<ClusterNameField />
</MockProviders>,
)

fireEvent.change(
screen.getByPlaceholderText('wizard.cluster.clusterName.placeholder'),
{target: {value: 'some-name'}},
)
})

it('should store the ClusterName in the cluster config', () => {
expect(mockSetState).toHaveBeenCalledTimes(1)
expect(mockSetState).toHaveBeenCalledWith(
['app', 'wizard', 'clusterName'],
'some-name',
)
})
})

describe('when user is editing a cluster', () => {
beforeEach(() => {
mockStore.getState.mockReturnValue({
app: {wizard: {editing: true}},
})
})

describe('when user tries to fill in the cluster name', () => {
beforeEach(() => {
screen = render(
<MockProviders>
<ClusterNameField />
</MockProviders>,
)

userEvent.type(
screen.getByPlaceholderText('wizard.cluster.clusterName.placeholder'),
'some-name',
)
})

it('should be disabled', () => {
expect(mockSetState).toHaveBeenCalledTimes(0)
})
})
})
})
22 changes: 9 additions & 13 deletions frontend/src/old-pages/Configure/Configure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
WizardProps,
} from '@cloudscape-design/components'

import {Source, SourceHelpPanel, sourceValidate} from './Source'
import {Cluster, clusterValidate} from './Cluster'
import {
HeadNode,
Expand Down Expand Up @@ -50,13 +49,16 @@ import Layout from '../Layout'
import {useWizardSectionChangeLog} from '../../navigation/useWizardSectionChangeLog'
import {NonCancelableEventHandler} from '@cloudscape-design/components/internal/events'
import i18next from 'i18next'
import {pages, useWizardNavigation} from './useWizardNavigation'
import {
INITIAL_WIZARD_PAGE,
pages,
useWizardNavigation,
} from './useWizardNavigation'
import {ComputeFleetStatus} from '../../types/clusters'
import {useClusterPoll} from '../../components/useClusterPoll'
import InfoLink from '../../components/InfoLink'

const validators: {[key: string]: (...args: any[]) => boolean} = {
source: sourceValidate,
cluster: clusterValidate,
headNode: headNodeValidate,
storage: storageValidate,
Expand All @@ -73,9 +75,9 @@ function wizardShow(navigate: any) {
clearState(['app', 'wizard', 'clusterConfigYaml'])
clearState(['app', 'wizard', 'loaded'])
setState(['app', 'wizard', 'editing'], false)
setState(['app', 'wizard', 'page'], 'source')
setState(['app', 'wizard', 'page'], INITIAL_WIZARD_PAGE)
}
if (!page) setState(['app', 'wizard', 'page'], 'source')
if (!page) setState(['app', 'wizard', 'page'], INITIAL_WIZARD_PAGE)
navigate('/configure')
}
const loadingPath = ['app', 'wizard', 'source', 'loading']
Expand Down Expand Up @@ -103,7 +105,7 @@ function Configure() {
const open = useState(['app', 'wizard', 'dialog'])
const clusterName = useState(['app', 'wizard', 'clusterName'])
const editing = useState(['app', 'wizard', 'editing'])
const currentPage = useState(['app', 'wizard', 'page']) || 'source'
const currentPage = useState(['app', 'wizard', 'page']) || INITIAL_WIZARD_PAGE
const [refreshing, setRefreshing] = React.useState(false)
let navigate = useNavigate()

Expand Down Expand Up @@ -174,7 +176,7 @@ function Configure() {
const showSecondaryActions = () => {
return (
<SpaceBetween direction="horizontal" size="xs">
{currentPage !== 'source' && currentPage !== 'create' && (
{currentPage !== 'create' && (
<Button
loading={refreshing}
onClick={handleRefresh}
Expand Down Expand Up @@ -246,12 +248,6 @@ function Configure() {
secondaryActions={showSecondaryActions()}
isLoadingNextStep={refreshing}
steps={[
{
title: t('wizard.source.title'),
description: t('wizard.source.description'),
content: <Source />,
info: <InfoLink helpPanel={<SourceHelpPanel />} />,
},
{
title: t('wizard.cluster.title'),
description: t('wizard.cluster.description'),
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/old-pages/Configure/useWizardNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import {getState, setState, updateState, useState} from '../../store'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'js-y... Remove this comment to see the full error message
import jsyaml from 'js-yaml'

const pages = ['source', 'cluster', 'headNode', 'queues', 'storage', 'create']
const pages = ['cluster', 'headNode', 'queues', 'storage', 'create']
export const INITIAL_WIZARD_PAGE = pages[0]

export const useWizardNavigation = (validate: (page: string) => boolean) => {
const currentPage = useState(['app', 'wizard', 'page']) || 'source'
const currentPage = useState(['app', 'wizard', 'page']) || INITIAL_WIZARD_PAGE

return (reason: string, requestedStepIndex: number) => {
switch (reason) {
Expand All @@ -19,7 +20,7 @@ export const useWizardNavigation = (validate: (page: string) => boolean) => {
handleStep(currentPage, pages[requestedStepIndex], validate)
break
default:
setPage('source')
setPage(INITIAL_WIZARD_PAGE)
}
}
}
Expand Down