Skip to content

Commit

Permalink
Merge pull request #4762 from abhinandan13jan/terminal
Browse files Browse the repository at this point in the history
Feat(cli-terminal): Create cli-terminal
  • Loading branch information
openshift-merge-robot committed Apr 7, 2020
2 parents 74f208b + 14e0cd4 commit 4bbaa7e
Show file tree
Hide file tree
Showing 28 changed files with 497 additions and 113 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
import * as React from 'react';
import { Button } from '@patternfly/react-core';
import CloudShellBody from './CloudShellBody';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RootState } from '@console/internal/redux';
import { isCloudShellExpanded } from '../../redux/reducers/cloud-shell-reducer';
import { toggleCloudShellExpanded } from '../../redux/actions/cloud-shell-actions';
import cloudShellConfirmationModal from './cloudShellConfirmationModal';
import CloudShellDrawer from './CloudShellDrawer';
import CloudShellTerminal from './CloudShellTerminal';

const CloudShell: React.FC = () => {
const [open, setOpen] = React.useState(false);
return (
<>
{/* Remove this button once actual terminal is in place */}
<Button variant="control" onClick={() => setOpen(!open)}>
Open Drawer
</Button>
<CloudShellDrawer open={open} onClose={() => setOpen(false)}>
<CloudShellBody />
</CloudShellDrawer>
</>
);
type StateProps = {
open: boolean;
};

export default CloudShell;
type DispatchProps = {
onClose: () => void;
};

type CloudShellProps = StateProps & DispatchProps;

const CloudShell: React.FC<CloudShellProps> = ({ open, onClose }) => {
const toggleWithModal = () => cloudShellConfirmationModal(onClose);
return open ? (
<CloudShellDrawer onClose={toggleWithModal}>
<CloudShellTerminal />
</CloudShellDrawer>
) : null;
};

const stateToProps = (state: RootState): StateProps => ({
open: isCloudShellExpanded(state),
});

const dispatchToProps = (dispatch: Dispatch): DispatchProps => ({
onClose: () => dispatch(toggleCloudShellExpanded()),
});

export default connect<StateProps, DispatchProps>(stateToProps, dispatchToProps)(CloudShell);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.co-cloud-shell-drawer {
&__heading {
padding-left: var(--pf-global--spacer--md);
font-weight: bold;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react';
import { Tooltip, Flex, FlexItem, FlexModifiers, Button } from '@patternfly/react-core';
import { CloseIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
import { Drawer } from '@console/shared';
import Drawer from '@console/shared/src/components/drawer/Drawer';
import MinimizeRestoreButton from './MinimizeRestoreButton';

import './CloudShellDrawer.scss';

export type CloudShellDrawerProps = {
open: boolean;
type CloudShellDrawerProps = {
onClose: () => void;
};

Expand All @@ -17,8 +17,8 @@ const getMastheadHeight = (): number => {
return height;
};

const CloudShellDrawer: React.FC<CloudShellDrawerProps> = ({ open, children, onClose }) => {
const [height, setHeight] = React.useState(318);
const CloudShellDrawer: React.FC<CloudShellDrawerProps> = ({ children, onClose }) => {
const [height, setHeight] = React.useState(326);
const [expanded, setExpanded] = React.useState<boolean>(true);
const onMRButtonClick = (expandedState: boolean) => {
setExpanded(!expandedState);
Expand All @@ -28,16 +28,14 @@ const CloudShellDrawer: React.FC<CloudShellDrawerProps> = ({ open, children, onC
setHeight(resizeHeight);
};
const header = (
<Flex>
<FlexItem className="ocs-terminal-drawer__heading">
<b>Command Line Terminal</b>
</FlexItem>
<Flex style={{ flexGrow: 1 }}>
<FlexItem className="co-cloud-shell-drawer__heading">Command Line Terminal</FlexItem>
<FlexItem breakpointMods={[{ modifier: FlexModifiers['align-right'] }]}>
<Tooltip content="Open terminal in new tab">
<Button
variant="plain"
component="a"
// change this once we can open teeminal in new tab
// change this once we can open terminal in new tab
href={null}
target="_blank"
aria-label="Open terminal in new tab"
Expand All @@ -52,26 +50,30 @@ const CloudShellDrawer: React.FC<CloudShellDrawerProps> = ({ open, children, onC
onClick={onMRButtonClick}
/>
<Tooltip content="Close terminal">
<Button variant="plain" type="button" onClick={onClose} aria-label="Close Terminal">
<Button
variant="plain"
data-test-id="cloudshell-terminal-close"
type="button"
onClick={onClose}
aria-label="Close terminal"
>
<CloseIcon />
</Button>
</Tooltip>
</FlexItem>
</Flex>
);
return (
open && (
<Drawer
open={expanded}
height={height}
header={header}
maxHeight={`calc(100vh - ${getMastheadHeight()}px)`}
onChange={handleChange}
resizable
>
{children}
</Drawer>
)
<Drawer
open={expanded}
height={height}
header={header}
maxHeight={`calc(100vh - ${getMastheadHeight()}px)`}
onChange={handleChange}
resizable
>
{children}
</Drawer>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RootState } from '@console/internal/redux';
import { TerminalIcon } from '@patternfly/react-icons';
import { isCloudShellExpanded } from '../../redux/reducers/cloud-shell-reducer';
import { Button, ToolbarItem, Tooltip } from '@patternfly/react-core';
import { connectToFlags, WithFlagsProps } from '@console/internal/reducers/features';
import { FLAG_DEVWORKSPACE } from '../../consts';
import { toggleCloudShellExpanded } from '../../redux/actions/cloud-shell-actions';
import { useAccessReview } from '@console/internal/components/utils';
import { WorkspaceModel } from '../../models';
import cloudShellConfirmationModal from './cloudShellConfirmationModal';

type DispatchProps = {
onClick: () => void;
};

type StateProps = {
open?: boolean;
};

type Props = WithFlagsProps & StateProps & DispatchProps;

// TODO use proper namespace and resource name
const namespace = 'che-workspace-controller';
const name = 'cloudshell-userid';

const ClouldShellMastheadButton: React.FC<Props> = ({ flags, onClick, open }) => {
const editAccess = useAccessReview({
group: WorkspaceModel.apiGroup,
resource: WorkspaceModel.plural,
verb: 'create',
name,
namespace,
});

const toggleTerminal = () => {
if (open) {
return cloudShellConfirmationModal(onClick);
}
return onClick();
};

if (!editAccess || !flags[FLAG_DEVWORKSPACE]) {
return null;
}

return (
<ToolbarItem>
<Tooltip content={open ? 'Close Terminal' : 'Open command line terminal'}>
<Button variant="plain" aria-label="Command Line Terminal" onClick={toggleTerminal}>
<TerminalIcon className="co-masthead-icon" />
</Button>
</Tooltip>
</ToolbarItem>
);
};

const cloudshellStateToProps = (state: RootState): StateProps => ({
open: isCloudShellExpanded(state),
});

const cloudshellPropsToState = (dispatch: Dispatch): DispatchProps => ({
onClick: () => dispatch(toggleCloudShellExpanded()),
});

export default connect<StateProps, DispatchProps>(
cloudshellStateToProps,
cloudshellPropsToState,
)(connectToFlags(FLAG_DEVWORKSPACE)(ClouldShellMastheadButton));
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as React from 'react';
import { referenceForModel, k8sCreate } from '@console/internal/module/k8s';
import { Firehose, FirehoseResource, FirehoseResult } from '@console/internal/components/utils';

import { WorkspaceModel } from '../../models';
import { newCloudShellWorkSpace, CloudShellResource } from './utils/cloudshell-resource';
import CloudShellTerminalFrame from './CloudShellTerminalFrame';

// TODO use proper namespace and resource name
const namespace = 'che-workspace-controller';
const name = 'cloudshell-userid';

const Inner: React.FC<{ cloudShell?: FirehoseResult<CloudShellResource[]> }> = ({ cloudShell }) => {
const loaded = cloudShell?.loaded;
const data = cloudShell?.data?.[0];
React.useEffect(() => {
if (loaded && data == null) {
k8sCreate(WorkspaceModel, newCloudShellWorkSpace(name, namespace));
}
}, [loaded, data]);

const running = data?.status?.phase === 'Running';
const url = data?.status?.ideUrl;
return <CloudShellTerminalFrame loading={!running} url={url} />;
};

const CloudShellTerminal: React.FC = () => {
const resources: FirehoseResource[] = [
{
kind: referenceForModel(WorkspaceModel),
namespace,
prop: `cloudShell`,
isList: true,
fieldSelector: `metadata.name=${name}`,
},
];

return (
<Firehose resources={resources}>
<Inner />
</Firehose>
);
};

export default CloudShellTerminal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.co-cloud-shell-terminal-frame {
background-color: #000;
height: 100%;

& > iframe {
height: 100%;
width: 100%;
border: 0;
padding: 4px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react';
import { LoadingBox } from '@console/internal/components/utils';
import './CloudShellTerminalFrame.scss';

type CloudShellTerminalFrameProps = {
loading?: boolean;
url?: string;
};

const CloudShellTerminalFrame: React.FC<CloudShellTerminalFrameProps> = ({ loading, url }) => (
<div className="co-cloud-shell-terminal-frame">
{loading ? <LoadingBox /> : <iframe title="Command Line Terminal" src={url} />}
</div>
);

export default CloudShellTerminalFrame;
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,11 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import CloudShellDrawer from '../CloudShellDrawer';
import { Drawer } from '@console/shared';
import { Button } from '@patternfly/react-core';

describe('CloudShellDrawerComponent', () => {
it('should not exist if open is not True', () => {
const wrapper = shallow(<CloudShellDrawer open={false} onClose={() => null} />);
expect(wrapper.isEmptyRender()).toEqual(true);
});

it('should exist when open is set to true', () => {
const wrapper = shallow(<CloudShellDrawer open onClose={() => null} />);
expect(wrapper.find(Drawer).exists()).toEqual(true);
});

it('should render children as Drawer children when present', () => {
const wrapper = shallow(
<CloudShellDrawer open onClose={() => null}>
<CloudShellDrawer onClose={() => null}>
<p>Terminal content</p>
</CloudShellDrawer>,
);
Expand All @@ -32,16 +21,16 @@ describe('CloudShellDrawerComponent', () => {
it('should call onClose when clicked on close button', () => {
const onClose = jest.fn();
const wrapper = shallow(
<CloudShellDrawer open onClose={onClose}>
<CloudShellDrawer onClose={onClose}>
<p>Terminal content</p>
</CloudShellDrawer>,
);
const buttons = wrapper
const closeButton = wrapper
.find(Drawer)
.shallow()
.find(Button);
expect(buttons.at(1).props()['aria-label']).toEqual('Close Terminal');
buttons.at(1).simulate('click');
.find('[data-test-id="cloudshell-terminal-close"]');
expect(closeButton.props()['aria-label']).toEqual('Close terminal');
closeButton.simulate('click');
expect(onClose).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { LoadingBox } from '@console/internal/components/utils';
import CloudShellTerminalFrame from '../CloudShellTerminalFrame';

describe('CloudShellTerminalFrame', () => {
it('should render LoadingBox', () => {
const wrapper = shallow(<CloudShellTerminalFrame loading />);
expect(wrapper.find(LoadingBox).exists()).toBe(true);
expect(wrapper.find('iframe').exists()).toBe(false);

wrapper.setProps({ loading: false });
expect(wrapper.find(LoadingBox).exists()).toBe(false);
});

it('should render iframe', () => {
const wrapper = shallow(<CloudShellTerminalFrame url="test" />);
const iframe = wrapper.find('iframe');
expect(iframe.props().src).toBe('test');
});
});

0 comments on commit 4bbaa7e

Please sign in to comment.