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
patternfly#6374)

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 authored Oct 19, 2021
1 parent f26f93c commit 7be4bc4
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 7be4bc4

Please sign in to comment.