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
3 changes: 3 additions & 0 deletions frontend/locales/en/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@
"actions": {
"cancel": "Cancel",
"create": "Create cluster",
"createFromWizard": "Use interface",
"createFromTemplate": "Upload a template",
"createFromCluster": "From another cluster",
"edit": "Edit",
"start": "Start fleet",
"stop": "Stop fleet",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// 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 {
ButtonDropdown,
ButtonDropdownProps,
} from '@cloudscape-design/components'
import {CancelableEventHandler} from '@cloudscape-design/components/internal/events'
import React, {useCallback, useMemo} from 'react'
import {useTranslation} from 'react-i18next'
import {NavigateFunction, useNavigate} from 'react-router-dom'
import {GetConfiguration} from '../../../model'
import {setState} from '../../../store'
import loadTemplate from '../../Configure/util'
import {HiddenFileUpload} from '../../../components/HiddenFileUpload'
// @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'
import {FromClusterModal} from '../FromClusterModal/FromClusterModal'

const loadingPath = ['app', 'wizard', 'source', 'loading']

function copyFrom(sourceClusterName: any) {
setState(loadingPath, true)
GetConfiguration(sourceClusterName, (configuration: any) => {
loadTemplate(jsyaml.load(configuration), () => setState(loadingPath, false))
})
}

interface Props {
openWizard: (navigate: NavigateFunction) => void
}

export const CreateButtonDropdown: React.FC<Props> = ({openWizard}) => {
const {t} = useTranslation()
const [isFileDialogOpen, setIsFileDialogOpen] = React.useState(false)
const [isModalVisible, setIsModalVisible] = React.useState(false)

const navigate = useNavigate()

const onModalDismiss = useCallback(() => {
setIsModalVisible(false)
}, [])

const onCreate = useCallback(
(name: string) => {
copyFrom(name)
openWizard(navigate)
},
[navigate, openWizard],
)

const onFileSelectorDismiss = useCallback(() => {
setIsFileDialogOpen(false)
}, [])

const onFileChange = useCallback(
(data: string) => {
setIsFileDialogOpen(false)
setState(loadingPath, true)
loadTemplate(jsyaml.load(data), () => setState(loadingPath, false))
openWizard(navigate)
},
[navigate, openWizard],
)

const onCreateClick: CancelableEventHandler<ButtonDropdownProps.ItemClickDetails> =
React.useCallback(
({detail}) => {
switch (detail.id) {
case 'wizard':
openWizard(navigate)
return
case 'template':
setIsFileDialogOpen(true)
return
case 'from-cluster':
setIsModalVisible(true)
return
}
},
[navigate, openWizard],
)

const createDropdownItems: ButtonDropdownProps.Item[] = useMemo(
() => [
{
id: 'wizard',
text: t('cluster.list.actions.createFromWizard'),
},
{
id: 'template',
text: t('cluster.list.actions.createFromTemplate'),
},
{
id: 'from-cluster',
text: t('cluster.list.actions.createFromCluster'),
},
],
[t],
)

return (
<>
<ButtonDropdown
variant="primary"
items={createDropdownItems}
onItemClick={onCreateClick}
>
{t('cluster.list.actions.create')}
</ButtonDropdown>
<HiddenFileUpload
open={isFileDialogOpen}
onDismiss={onFileSelectorDismiss}
onChange={onFileChange}
/>
<FromClusterModal
visible={isModalVisible}
onDismiss={onModalDismiss}
onCreate={onCreate}
/>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import wrapper from '@cloudscape-design/components/test-utils/dom'
import {Store} from '@reduxjs/toolkit'
import {render, RenderResult} from '@testing-library/react'
import i18n from 'i18next'
import {mock} from 'jest-mock-extended'
import {I18nextProvider, initReactI18next} from 'react-i18next'
import {Provider} from 'react-redux'
import {BrowserRouter} from 'react-router-dom'
import {CreateButtonDropdown} from '../CreateButtonDropdown'

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

const mockStore = mock<Store>()

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

describe('given a dropdown button to create a cluster', () => {
let screen: RenderResult
let mockOpenWizard: jest.Mock

beforeEach(() => {
mockOpenWizard = jest.fn()

screen = render(
<MockProviders>
<CreateButtonDropdown openWizard={mockOpenWizard} />
</MockProviders>,
)
})

describe('when user selects the option to create a cluster using the wizard', () => {
beforeEach(() => {
const buttonDropdown = wrapper(screen.container).findButtonDropdown()!
buttonDropdown.openDropdown()
buttonDropdown.findItemById('wizard')?.click()
})

it('should open the cluster creation wizard', () => {
expect(mockOpenWizard).toHaveBeenCalledTimes(1)
})
})
})