Skip to content

Commit

Permalink
feat(react-console): accept random children in AccessConsole component
Browse files Browse the repository at this point in the history
So far we were supporting children of three types,
SerialConsole, DesktopViewer and VncConsole.

This component is consumed by `cockpit machines` product and it's
used for exposing consoles of Virtual Machines.
A Virtual Machines however can have multiple consoles of one type; for example 2
different serial consoles, something which was not supported until this
commit.

For this reason we need to extend this component to accept also custom
children apart from a single from each from the predefined types.

Also start respecting the order of the children to display the options
in the AccessConsoles dropdown.
  • Loading branch information
KKoukiou committed Oct 12, 2021
1 parent 723b927 commit 412e5d2
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ export interface AccessConsolesProps {
children?: React.ReactElement[] | React.ReactNode;
/** Placeholder text for the console selection */
textSelectConsoleType?: string;
/** The value for the Serial Console option */
/** The value for the Serial Console option. This can be overriden by the type property of the child component */
textSerialConsole?: string;
/** The value for the VNC Console option */
/** The value for the VNC Console option. This can be overriden by the type property of the child component */
textVncConsole?: string;
/** The value for the Desktop Viewer Console option */
/** The value for the Desktop Viewer Console option. This can be overriden by the type property of the child component */
textDesktopViewerConsole?: string;
/** Initial selection of the Select */
preselectedType?: string; // NONE_TYPE | SERIAL_CONSOLE_TYPE | VNC_CONSOLE_TYPE | DESKTOP_VIEWER_CONSOLE_TYPE;
Expand All @@ -50,16 +50,11 @@ export const AccessConsoles: React.FunctionComponent<AccessConsolesProps> = ({
}) => {
const [type, setType] = React.useState(preselectedType);
const [isOpen, setIsOpen] = React.useState(false);

const isChildOfTypePresent = (type: string) => {
let found = false;
React.Children.forEach(children as React.ReactElement[], (child: any) => {
found = found || isChildOfType(child, type);
});

return found;
const typeMap = {
[textSerialConsole]: SERIAL_CONSOLE_TYPE,
[textVncConsole]: VNC_CONSOLE_TYPE,
[textDesktopViewerConsole]: DESKTOP_VIEWER_CONSOLE_TYPE
};

const getConsoleForType = (type: string) =>
React.Children.map(children as React.ReactElement[], (child: any) => {
if (getChildTypeName(child) === type) {
Expand All @@ -73,31 +68,26 @@ export const AccessConsoles: React.FunctionComponent<AccessConsolesProps> = ({
setIsOpen(isOpen);
};

const items = {
[SERIAL_CONSOLE_TYPE]: textSerialConsole,
[VNC_CONSOLE_TYPE]: textVncConsole,
[DESKTOP_VIEWER_CONSOLE_TYPE]: textDesktopViewerConsole
};

const selectOptions = [];
if (isChildOfTypePresent(VNC_CONSOLE_TYPE)) {
selectOptions.push(<SelectOption key={VNC_CONSOLE_TYPE} id={VNC_CONSOLE_TYPE} value={items[VNC_CONSOLE_TYPE]} />);
}
if (isChildOfTypePresent(SERIAL_CONSOLE_TYPE)) {
selectOptions.push(
<SelectOption key={SERIAL_CONSOLE_TYPE} id={SERIAL_CONSOLE_TYPE} value={items[SERIAL_CONSOLE_TYPE]} />
);
}
if (isChildOfTypePresent(DESKTOP_VIEWER_CONSOLE_TYPE)) {
selectOptions.push(
<SelectOption
key={DESKTOP_VIEWER_CONSOLE_TYPE}
id={DESKTOP_VIEWER_CONSOLE_TYPE}
value={items[DESKTOP_VIEWER_CONSOLE_TYPE]}
/>
);
}
const selectOptions: any[] = [];

React.Children.toArray(children as React.ReactElement[]).forEach((child: any) => {
if (isChildOfType(child, VNC_CONSOLE_TYPE)) {
selectOptions.push(<SelectOption key={VNC_CONSOLE_TYPE} id={VNC_CONSOLE_TYPE} value={textVncConsole} />);
} else if (isChildOfType(child, SERIAL_CONSOLE_TYPE)) {
selectOptions.push(<SelectOption key={SERIAL_CONSOLE_TYPE} id={SERIAL_CONSOLE_TYPE} value={textSerialConsole} />);
} else if (isChildOfType(child, DESKTOP_VIEWER_CONSOLE_TYPE)) {
selectOptions.push(
<SelectOption
key={DESKTOP_VIEWER_CONSOLE_TYPE}
id={DESKTOP_VIEWER_CONSOLE_TYPE}
value={textDesktopViewerConsole}
/>
);
} else {
const typeText = getChildTypeName(child);
selectOptions.push(<SelectOption key={typeText} id={typeText} value={typeText} />);
}
});
return (
<div className={css(styles.console)}>
{React.Children.toArray(children).length > 1 && (
Expand All @@ -108,10 +98,14 @@ export const AccessConsoles: React.FunctionComponent<AccessConsolesProps> = ({
toggleId="pf-c-console__type-selector"
variant={SelectVariant.single}
onSelect={(_, selection, __) => {
setType(Object.keys(items).find(key => items[key] === selection));
if ((selection as string) in typeMap) {
setType(typeMap[selection as string]);
} else {
setType(selection as string);
}
setIsOpen(false);
}}
selections={type !== NONE_TYPE ? items[type] : null}
selections={type !== NONE_TYPE ? type : null}
isOpen={isOpen}
onToggle={onToggle}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ test('AccessConsoles with VncConsole as a single child', () => {
test('AccessConsoles with SerialConsole and VncConsole as children', () => {
const view = shallow(
<AccessConsoles>
<SerialConsole onConnect={jest.fn()} onDisconnect={jest.fn()} status={LOADING} />
<VncConsole host="foo.bar.host" textDisconnected="Disconnected state text" />
<SerialConsole onConnect={jest.fn()} onDisconnect={jest.fn()} status={LOADING} />
</AccessConsoles>
);
expect(view).toMatchSnapshot();
Expand Down Expand Up @@ -73,8 +73,8 @@ test('AccessConsoles with preselected SerialConsole', () => {
test('AccessConsoles switching SerialConsole and VncConsole', () => {
const wrapper = mount(
<AccessConsoles>
<SerialConsole onConnect={jest.fn()} onDisconnect={jest.fn()} status={LOADING} />
<MyVncConsoleTestWrapper type={VNC_CONSOLE_TYPE} />
<SerialConsole onConnect={jest.fn()} onDisconnect={jest.fn()} status={LOADING} />
</AccessConsoles>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,43 +252,14 @@ Array [
role="presentation"
>
<button
aria-selected={true}
className="pf-c-select__menu-item pf-m-selected"
aria-selected={null}
className="pf-c-select__menu-item"
onClick={[Function]}
onKeyDown={[Function]}
role="option"
type="button"
>
Serial console
<span
className="pf-c-select__menu-item-icon"
>
<CheckIcon
aria-hidden={true}
color="currentColor"
noVerticalAlign={false}
size="sm"
>
<svg
aria-hidden={true}
aria-labelledby={null}
fill="currentColor"
height="1em"
role="img"
style={
Object {
"verticalAlign": "-0.125em",
}
}
viewBox="0 0 512 512"
width="1em"
>
<path
d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"
/>
</svg>
</CheckIcon>
</span>
</button>
</li>,
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ beta: true
AccessConsoles lives in its own package at [`@patternfly/react-console`](https://www.npmjs.com/package/@patternfly/react-console)

import { AccessConsoles, SerialConsole, VncConsole, DesktopViewer } from '@patternfly/react-console';
import { SerialConsoleCustom } from './SerialConsoleCustom.jsx';

## Examples

### Basic Usage
```js
import React from 'react';
import { AccessConsoles, SerialConsole, VncConsole, DesktopViewer } from '@patternfly/react-console';
import { SerialConsoleCustom } from './SerialConsoleCustom.jsx';

AccessConsolesVariants = () => {
const [status, setStatus] = React.useState('disconnected');
Expand All @@ -25,6 +27,7 @@ AccessConsolesVariants = () => {

return (
<AccessConsoles preselectedType="SerialConsole">
<VncConsole host="localhost" port="9090" encrypt shared />
<SerialConsole
onConnect={() => {
setStatus('loading');
Expand All @@ -37,9 +40,10 @@ AccessConsolesVariants = () => {
}}
ref={ref}
/>
<SerialConsoleCustom type='Serial Console pty2' />
<DesktopViewer spice={{ address: '127.0.0.1', port: '5900' }} vnc={{ address: '127.0.0.1', port: '5901' }} />
<VncConsole host="localhost" port="9090" encrypt shared />
</AccessConsoles>
);
};
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { debounce } from '@patternfly/react-core';
import { SerialConsole } from '@patternfly/react-console';

export const SerialConsoleCustom = () => {
const [status, setStatus] = React.useState('disconnected');
const setConnected = React.useRef(debounce(() => setStatus('connected'), 3000)).current;
const ref2 = React.createRef();

return (
<SerialConsole
onConnect={() => {
setStatus('loading');
setConnected();
}}
onDisconnect={() => setStatus('disconnected')}
onData={data => {
ref2.current.onDataReceived(data);
}}
status={status}
ref={ref2}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const ConsolesDemo: React.FC = () => {
return (
<div className="consoles-demo-area">
<AccessConsoles preselectedType="SerialConsole">
<SerialConsoleCustom type="SerialConsole" typeText="Serial Console pty2" />
<SerialConsole
onConnect={() => {
setStatus('loading');
Expand All @@ -30,3 +31,24 @@ export const ConsolesDemo: React.FC = () => {
);
};
ConsolesDemo.displayName = 'ConsolesDemo';

const SerialConsoleCustom: React.FC<{ type: string; typeText: string }> = () => {
const [status, setStatus] = React.useState('disconnected');
const setConnected = React.useRef(debounce(() => setStatus('connected'), 3000)).current;
const ref2 = React.createRef<any>();

return (
<SerialConsole
onConnect={() => {
setStatus('loading');
setConnected();
}}
onDisconnect={() => setStatus('disconnected')}
onData={(data: string) => {
ref2.current.onDataReceived(data);
}}
status={status}
ref={ref2}
/>
);
};

0 comments on commit 412e5d2

Please sign in to comment.