Skip to content

Commit

Permalink
ACM-9268 KubeVirt hosted cluster creation wizard (stolostron#3274)
Browse files Browse the repository at this point in the history
* ACM-9268 KubeVirt hosted cluster creation wizard

Signed-off-by: zlayne <[email protected]>

* fix lint errors

Signed-off-by: zlayne <[email protected]>

* fix credentials test

Signed-off-by: zlayne <[email protected]>

* Update controlDataKubeVirt test

Signed-off-by: zlayne <[email protected]>

* fix lint

Signed-off-by: zlayne <[email protected]>

* add tests

Signed-off-by: zlayne <[email protected]>

---------

Signed-off-by: zlayne <[email protected]>
  • Loading branch information
zlayne authored Feb 7, 2024
1 parent 4a8b881 commit 72d8bfa
Show file tree
Hide file tree
Showing 22 changed files with 1,864 additions and 102 deletions.
30 changes: 30 additions & 0 deletions frontend/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@
"Attempts to delete objects known to be created by the policy when the policy is deleted.": "Attempts to delete objects known to be created by the policy when the policy is deleted.",
"Attribute filters": "Attribute filters",
"Attributes": "Attributes",
"Auto repair": "Auto repair",
"Automatically create namespace if it does not exist": "Automatically create namespace if it does not exist",
"Automatically runs remediation action that is defined in the source, if this feature is supported.": "Automatically runs remediation action that is defined in the source, if this feature is supported.",
"Automatically sync when cluster state changes": "Automatically sync when cluster state changes",
Expand Down Expand Up @@ -793,6 +794,7 @@
"Copy button": "Copy button",
"Copy this private URL to share": "Copy this private URL to share",
"Copy to clipboard": "Copy to clipboard",
"Core": "Core",
"Could not find the AgentClusterInstall resource.": "Could not find the AgentClusterInstall resource.",
"Could not process request because of invalid data.": "Could not process request because of invalid data.",
"count.labels": "{{count}} label",
Expand Down Expand Up @@ -986,6 +988,7 @@
"creation.ocp.instance.type": "Instance type",
"creation.ocp.machine.cidr": "Machine CIDR",
"creation.ocp.machine.cidr.placeholder": "Enter machine CIDR",
"creation.ocp.memoryGB": "Memory (GiB)",
"creation.ocp.memoryMB": "Memory (MiB)",
"creation.ocp.name": "Cluster name",
"creation.ocp.name.placeholder": "Enter cluster name",
Expand All @@ -994,6 +997,7 @@
"creation.ocp.node.controlplane.pool.title": "Control plane pool",
"creation.ocp.node.network.info": "Specify at least one network. Multiple are required for IPv6.",
"creation.ocp.node.network.title": "Network {{0}}",
"creation.ocp.node.pool.title": "Node pool {{0}}",
"creation.ocp.node.worker.pool.info": "One or more worker nodes will be created to run the container workloads in this cluster.",
"creation.ocp.node.worker.pool.title": "Worker pool {{0}}",
"creation.ocp.pool.name": "Pool name",
Expand Down Expand Up @@ -1282,13 +1286,15 @@
"Enter job tag with \",\" or \"enter\"": "Enter job tag with \",\" or \"enter\"",
"Enter memory limit": "Enter memory limit",
"Enter memory request": "Enter memory request",
"Enter node pool name": "Enter node pool name",
"Enter or select a Git URL": "Enter or select a Git URL",
"Enter or select a Helm URL": "Enter or select a Helm URL",
"Enter or select a repository path": "Enter or select a repository path",
"Enter or select a tracking revision": "Enter or select a tracking revision",
"Enter Red Hat OpenShift Container Platform pull secret": "Enter Red Hat OpenShift Container Platform pull secret",
"Enter search text": "Enter search text",
"Enter skip tag with \",\" or \"enter\"": "Enter skip tag with \",\" or \"enter\"",
"Enter Storage Class": "Enter Storage Class",
"Enter the Amazon Web Services credentials": "Enter the Amazon Web Services credentials",
"Enter the Ansible Automation Platform credentials": "Enter the Ansible Automation Platform credentials",
"Enter the Ansible Tower host URL": "Enter the Ansible Tower host URL",
Expand Down Expand Up @@ -1360,6 +1366,7 @@
"Error querying resource logs:": "Error querying resource logs:",
"Error querying search results": "Error querying search results",
"error.create.syntax": "Syntax error: {{0}}",
"Etcd storage class": "Etcd storage class",
"Ev3-series": "Ev3-series",
"EveryEvent": "EveryEvent",
"Examples: 512Mi, 2Gi": "Examples: 512Mi, 2Gi",
Expand Down Expand Up @@ -1589,6 +1596,7 @@
"Infrastructure provider credentials": "Infrastructure provider credentials",
"Insights data": "Insights data",
"install": "Install",
"Install operator": "Install operator",
"Install Plan Approval": "Install Plan Approval",
"Install plan approved": "Install plan approved",
"Install the operator": "Install the operator",
Expand Down Expand Up @@ -1651,6 +1659,7 @@
"Limits": "Limits",
"ListBox input field": "ListBox input field",
"Loading": "Loading",
"Loading etcd storage classes...": "Loading etcd storage classes...",
"Loading more...": "Loading more...",
"Loading namespaces...": "Loading namespaces...",
"Loading...": "Loading...",
Expand Down Expand Up @@ -1834,6 +1843,7 @@
"No clusters available": "No clusters available",
"No clusters found": "No clusters found",
"No conditions found": "No conditions found",
"No etcd storage classes. Enter an etcd storage classes name.": "No etcd storage classes. Enter an etcd storage classes name.",
"No history": "No history",
"No infrastructure environments found": "No infrastructure environments found",
"No infrastructure environments yet": "No infrastructure environments yet",
Expand Down Expand Up @@ -1861,6 +1871,7 @@
"no.running.clusters.in.pool": "There are no running clusters in the pool",
"Node pool": "Node pool",
"Node pool name": "Node pool name",
"Node pool replica": "Node pool replica",
"Node pools": "Node pools",
"Node pools cannot be added during hosted cluster upgrade.": "Node pools cannot be added during hosted cluster upgrade.",
"Node pools cannot be managed during hosted cluster upgrade.": "Node pools cannot be managed during hosted cluster upgrade.",
Expand Down Expand Up @@ -1918,6 +1929,7 @@
"OpenShift GitOps Operator is required to create ApplicationSets.": "OpenShift GitOps Operator is required to create ApplicationSets.",
"OpenShift release images unavailable": "OpenShift release images unavailable",
"OpenShift version": "OpenShift version",
"OpenShift Virtualization operator is required to create a cluster.": "OpenShift Virtualization operator is required to create a cluster.",
"openshift.cluster.manager": "OpenShift Cluster Manager",
"OpenStack clouds.yaml": "OpenStack clouds.yaml",
"Operator": "Operator",
Expand Down Expand Up @@ -1975,6 +1987,7 @@
"Permanently destroy clusters?": "Permanently destroy clusters?",
"Permanently remove node pool {{name}}?": "Permanently remove node pool {{name}}?",
"Persistent volume": "Persistent volume",
"Persistent volume storage class for etcd data volumes": "Persistent volume storage class for etcd data volumes",
"Personalize this view": "Personalize this view",
"Phase": "Phase",
"placeholder.creation.ocp.baseDomain": "Enter base DNS domain",
Expand Down Expand Up @@ -2220,7 +2233,9 @@
"Revision": "Revision",
"Role": "Role",
"Root volume": "Root volume",
"Root volume option": "Root volume option",
"Root volume size": "Root volume size",
"Root Volume Storage Class": "Root Volume Storage Class",
"Root volume type": "Root volume type",
"Rules": "Rules",
"Run an OpenShift cluster where the control plane and data plane are coupled. The control plane is hosted by a dedicated group of physical or virtual nodes and the network stack is shared.": "Run an OpenShift cluster where the control plane and data plane are coupled. The control plane is hosted by a dedicated group of physical or virtual nodes and the network stack is shared.",
Expand Down Expand Up @@ -2273,11 +2288,14 @@
"Select a namespace for the credential": "Select a namespace for the credential",
"Select a namespace to be able to select policies in that namespace.": "Select a namespace to be able to select policies in that namespace.",
"Select a policy set": "Select a policy set",
"Select a storage class": "Select a storage class",
"Select a template": "Select a template",
"Select a volume mode": "Select a volume mode",
"Select all": "Select all",
"Select all ({{count}} items)": "Select all ({{count}} item)",
"Select all ({{count}} items)_plural": "Select all ({{count}} items)",
"Select all clusters to be added to the cluster set and deselect all clusters to be removed from the cluster set.": "Select all clusters to be added to the cluster set and deselect all clusters to be removed from the cluster set.",
"Select an access mode": "Select an access mode",
"Select an active cloud name for the credential.": "Select an active cloud name for the credential.",
"Select an inventory": "Select an inventory",
"Select at least one cluster set to deploy application resources.": "Select at least one cluster set to deploy application resources.",
Expand All @@ -2286,6 +2304,7 @@
"Select clusters from the clusters in selected cluster sets using cluster labels. For a cluster to be be selected, the cluster must match all label selectors, label expressions, and claim expressions.": "Select clusters from the clusters in selected cluster sets using cluster labels. For a cluster to be be selected, the cluster must match all label selectors, label expressions, and claim expressions.",
"Select configured clusters from the chosen cluster set by using cluster labels, which filters on the clusters. To use cluster labels, your cluster must match all label selectors, label expressions, and claim expressions.": "Select configured clusters from the chosen cluster set by using cluster labels, which filters on the clusters. To use cluster labels, your cluster must match all label selectors, label expressions, and claim expressions.",
"Select container": "Select container",
"Select etcd storage class": "Select etcd storage class",
"Select label value": "Select label value",
"Select namespace": "Select namespace",
"Select none": "Select none",
Expand Down Expand Up @@ -2336,6 +2355,7 @@
"Simple Table": "Simple Table",
"Single cluster": "Single cluster",
"Single node OpenShift": "Single node OpenShift",
"Size (GiB)": "Size (GiB)",
"Skip tags": "Skip tags",
"Some policies have the Prune parameter set.": "Some policies have the Prune parameter set.",
"Some resources failed to deploy. Use View resource YAML link to view the details.": "Some resources failed to deploy. Use View resource YAML link to view the details.",
Expand Down Expand Up @@ -2901,6 +2921,15 @@
"tooltip.creation.ocp.machine.cidr": "A block of IP addresses used by the OpenShift Container Platform hosts. The address block must not overlap any other network block. The default value is 10.0.0.0/16.",
"tooltip.creation.ocp.memoryMB": "Optional. Memory of the VM in MiB.",
"tooltip.creation.ocp.name": "The unique name of your cluster. The value must be a string that contains lowercase alphanumeric values, such as dev. Cannot be changed after creation.",
"tooltip.creation.ocp.node.pool.autorepair": "Enables machine auto-repair with machine health checks",
"tooltip.creation.ocp.node.pool.core.count": "Number of cores assigned to each worker node VM",
"tooltip.creation.ocp.node.pool.memoryGB": "Memory assigned to each worker node VM",
"tooltip.creation.ocp.node.pool.name": "The unique name of your node pool. The value must be a string that contains lowercase alphanumeric values, such as dev. Cannot be changed after creation.",
"tooltip.creation.ocp.node.pool.replica.count": "The number of Worker Nodes in the cluster",
"tooltip.creation.ocp.node.pool.root.vol.accessmode": "Access mode used for the worker node VM’s root volume",
"tooltip.creation.ocp.node.pool.root.vol.size": "The size of the underlying root volume used for the worker node VM",
"tooltip.creation.ocp.node.pool.root.vol.storageclass": "Storage class used for the worker node VM’s root volume",
"tooltip.creation.ocp.node.pool.root.vol.volumemode": "Volume Mode used for the worker node VM’s root volume",
"tooltip.creation.ocp.otp.instance.type": "The OpenStack compute flavor.",
"tooltip.creation.ocp.ovirt.diskSizeGB": "Required if you use <machine-pool>.platform.ovirt.osDisk. Size of the disk in GiB.",
"tooltip.creation.ocp.pool.name": "The name for your worker pool.",
Expand Down Expand Up @@ -3073,6 +3102,7 @@
"Violations": "Violations",
"VM size": "VM size",
"VMware": "VMware",
"Volume mode": "Volume mode",
"vSphere cluster name": "vSphere cluster name",
"vSphere datacenter": "vSphere datacenter",
"vSphere default datastore": "vSphere default datastore",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/NavigationPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { LocationDescriptor } from 'history'
import { useContext, useMemo } from 'react'
import { generatePath } from 'react-router'
import { useHistory, useLocation } from 'react-router-dom'
import { Cluster } from './resources'
import { LostChangesContext } from './components/LostChanges'
import { Cluster } from './resources'

export const UNKNOWN_NAMESPACE = '~managed-cluster'

Expand Down Expand Up @@ -52,6 +52,7 @@ export enum NavigationPath {
createDiscoverHost = '/multicloud/infrastructure/clusters/create/discover-host',
createCluster = '/multicloud/infrastructure/clusters/create',
createAWSCLI = '/multicloud/infrastructure/clusters/create/aws/cli',
createKubeVirt = '/multicloud/infrastructure/clusters/create/kubevirt',
createKubeVirtCLI = '/multicloud/infrastructure/clusters/create/kubevirt/cli',
editCluster = '/multicloud/infrastructure/clusters/edit/:namespace/:name',
clusterDetails = '/multicloud/infrastructure/clusters/details/:namespace/:name',
Expand Down
34 changes: 23 additions & 11 deletions frontend/src/components/TemplateEditor/controls/ControlPanel.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
/* Copyright Contributors to the Open Cluster Management project */
'use strict'

import React from 'react'
import PropTypes from 'prop-types'
import { Alert, Button } from '@patternfly/react-core'
import { PlusCircleIcon, TrashIcon } from '@patternfly/react-icons'
import classNames from 'classnames'
import PropTypes from 'prop-types'
import React from 'react'
import '../css/control-panel.css'
import ControlPanelAccordion from './ControlPanelAccordion'
import ControlPanelTextInput from './ControlPanelTextInput'
import ControlPanelBoolean from './ControlPanelBoolean'
import ControlPanelCards from './ControlPanelCards'
import ControlPanelCheckbox from './ControlPanelCheckbox'
import ControlPanelComboBox from './ControlPanelComboBox'
import ControlPanelTextArea from './ControlPanelTextArea'
import ControlPanelLabels from './ControlPanelLabels'
import ControlPanelMultiSelect from './ControlPanelMultiSelect'
import ControlPanelNumber from './ControlPanelNumber'
import ControlPanelCheckbox from './ControlPanelCheckbox'
import ControlPanelPrompt from './ControlPanelPrompt'
import ControlPanelSingleSelect from './ControlPanelSingleSelect'
import ControlPanelSkeleton from './ControlPanelSkeleton'
import ControlPanelTextArea from './ControlPanelTextArea'
import ControlPanelTextInput from './ControlPanelTextInput'
import ControlPanelTreeSelect from './ControlPanelTreeSelect'
import ControlPanelMultiSelect from './ControlPanelMultiSelect'
import ControlPanelCards from './ControlPanelCards'
import ControlPanelLabels from './ControlPanelLabels'
import ControlPanelValues from './ControlPanelValues'
import ControlPanelWizard from './ControlPanelWizard'
import ControlPanelPrompt from './ControlPanelPrompt'
import ControlPanelSkeleton from './ControlPanelSkeleton'
import '../css/control-panel.css'

class ControlPanel extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -546,6 +547,17 @@ class ControlPanel extends React.Component {
i18n={i18n}
/>
)
case 'boolean':
return (
<ControlPanelBoolean
key={controlId}
controlId={controlId}
control={control}
controlData={controlData}
handleChange={this.handleControlChange.bind(this, control)}
i18n={i18n}
/>
)
case 'checkbox':
case 'radio':
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* Copyright Contributors to the Open Cluster Management project */
'use strict'

import { Radio } from '@patternfly/react-core'
import PropTypes from 'prop-types'
import React from 'react'
import ControlPanelFormGroup from './ControlPanelFormGroup'

class ControlPanelBoolean extends React.Component {
static propTypes = {
control: PropTypes.object,
controlId: PropTypes.string,
handleChange: PropTypes.func,
i18n: PropTypes.func,
}

constructor(props) {
super(props)
this.state = {}
}

setControlRef = (control, ref) => {
control.ref = ref
}

render() {
const { controlId, control, controlData, handleChange, i18n } = this.props
const { tip, isTrue } = control

const onChange = () => {
control.isTrue = !isTrue
control.active = !isTrue
handleChange()
}

return (
<React.Fragment>
<div
style={{ flexDirection: 'column', alignItems: 'flex-start' }}
className="creation-view-controls-number"
ref={this.setControlRef.bind(this, control)}
>
<ControlPanelFormGroup i18n={i18n} controlId={controlId} control={control} controlData={controlData}>
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '40px' }}>
<Radio
aria-label={`${controlId}-true`}
id={`${controlId}-true`}
label={'True'}
isChecked={isTrue}
onChange={onChange}
data-testid={`radio-${controlId}`}
style={{ marginRight: 0 }}
/>
</div>
<div>
<Radio
aria-label={`${controlId}-false`}
id={`${controlId}-false`}
label={'False'}
isChecked={!isTrue}
onChange={onChange}
data-testid={`radio-${controlId}`}
style={{ marginRight: 0 }}
/>
</div>
</div>
</ControlPanelFormGroup>
</div>
<div style={{ fontSize: '14px', fontWeight: 'normal' }}>{tip}</div>
</React.Fragment>
)
}
}

export default ControlPanelBoolean
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* Copyright Contributors to the Open Cluster Management project */
'use strict'

import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ControlPanelBoolean from './ControlPanelBoolean'

import i18n from 'i18next'

const t = i18n.t.bind(i18n)

export const control = {
active: false,
name: 'Name',
tooltip: 'Application name',
controlData: [],
id: 'boolean',
type: 'boolean',
isTrue: false,
}

const fn = jest.fn()

describe('ControlPanelBoolean component', () => {
it('renders as expected', () => {
const Component = () => {
return <ControlPanelBoolean key={'key'} control={control} controlId={'controlId'} handleChange={fn} i18n={t} />
}

const { getByTestId, asFragment, rerender } = render(<Component />)
expect(asFragment()).toMatchSnapshot()

userEvent.click(getByTestId('controlId-true'))
expect(control.active).toBe(true)
expect(control.isTrue).toBe(true)

// control.active = 'true'
rerender(<Component />)
userEvent.click(getByTestId('controlId-false'))
expect(control.active).toBe(false)
expect(control.isTrue).toBe(false)
})
})
Loading

0 comments on commit 72d8bfa

Please sign in to comment.