From a71095146b170fa612be6ceadc6d0b684beb40e1 Mon Sep 17 00:00:00 2001 From: Oleksii Kurinnyi Date: Tue, 29 Aug 2023 15:43:55 +0300 Subject: [PATCH] feat: navigate to cluster console DevWorkspace object Signed-off-by: Oleksii Kurinnyi --- .../__snapshots__/index.spec.tsx.snap | 227 ++++++++++++++++++ .../EditorTools/__tests__/index.spec.tsx | 180 ++++++++++++++ .../components/EditorTools/index.module.css | 2 +- .../src/components/EditorTools/index.tsx | 139 +++++++---- .../contexts/ToggleBars/__mocks__/index.tsx | 27 +++ .../DevfileEditorTab/index.tsx | 2 +- .../DevworkspaceEditorTab/index.tsx | 2 +- .../workspace-adapter/__tests__/index.spec.ts | 20 +- .../src/services/workspace-adapter/index.ts | 11 + 9 files changed, 553 insertions(+), 57 deletions(-) create mode 100644 packages/dashboard-frontend/src/components/EditorTools/__tests__/__snapshots__/index.spec.tsx.snap create mode 100644 packages/dashboard-frontend/src/components/EditorTools/__tests__/index.spec.tsx create mode 100644 packages/dashboard-frontend/src/contexts/ToggleBars/__mocks__/index.tsx diff --git a/packages/dashboard-frontend/src/components/EditorTools/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/EditorTools/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 0000000000..faba0bec22 --- /dev/null +++ b/packages/dashboard-frontend/src/components/EditorTools/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,227 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EditorTools DevWorkspace snapshot 1`] = ` +
+
+
+ + + + + + + Cluster console + +
+
+
+ +
+
+
+ + + + + Download + +
+
+
+ +
+
+
+`; + +exports[`EditorTools Devfile snapshot 1`] = ` +
+
+
+ +
+
+
+ + + + + Download + +
+
+
+ +
+
+
+`; diff --git a/packages/dashboard-frontend/src/components/EditorTools/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/EditorTools/__tests__/index.spec.tsx new file mode 100644 index 0000000000..f1a66f51ee --- /dev/null +++ b/packages/dashboard-frontend/src/components/EditorTools/__tests__/index.spec.tsx @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import React from 'react'; +import getComponentRenderer, { screen } from '../../../services/__mocks__/getComponentRenderer'; +import { Provider } from 'react-redux'; +import EditorTools from '..'; +import { Store } from 'redux'; +import { FakeStoreBuilder } from '../../../store/__mocks__/storeBuilder'; +import devfileApi from '../../../services/devfileApi'; +import { ApplicationId } from '@eclipse-che/common'; + +jest.mock('../../../contexts/ToggleBars'); + +const mockClipboard = jest.fn(); +jest.mock('react-copy-to-clipboard', () => { + return { + __esModule: true, + default: (props: any) => { + return ( + + ); + }, + }; +}); + +const { renderComponent, createSnapshot } = getComponentRenderer(getComponent); + +const mockOnExpand = jest.fn(); +let store: Store; + +describe('EditorTools', () => { + const clusterConsole = { + id: ApplicationId.CLUSTER_CONSOLE, + url: 'https://console-url', + icon: 'https://console-icon-url', + title: 'Cluster console', + }; + + beforeEach(() => { + store = new FakeStoreBuilder() + .withClusterInfo({ + applications: [clusterConsole], + }) + .build(); + + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + + describe('Devfile', () => { + let devfile: devfileApi.Devfile; + + beforeEach(() => { + devfile = { + schemaVersion: '2.1.0', + metadata: { + name: 'my-project', + namespace: 'user-che', + }, + }; + }); + + test('snapshot', () => { + const snapshot = createSnapshot(devfile); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + test('expand and compress', () => { + renderComponent(devfile); + + /* expand the editor */ + + const expandButtonName = 'Expand'; + expect(screen.getByRole('button', { name: expandButtonName })).toBeTruthy; + + screen.getByRole('button', { name: expandButtonName }).click(); + expect(mockOnExpand).toHaveBeenCalledWith(true); + + /* compress the editor */ + + const compressButtonName = 'Compress'; + expect(screen.getByRole('button', { name: compressButtonName })).toBeTruthy; + + screen.getByRole('button', { name: compressButtonName }).click(); + expect(mockOnExpand).toHaveBeenCalledWith(false); + }); + + test('copy to clipboard', () => { + const mockCreateObjectURL = jest.fn().mockReturnValue('blob-url'); + URL.createObjectURL = mockCreateObjectURL; + + renderComponent(devfile); + + const copyButtonName = 'Copy to clipboard'; + expect(screen.queryByRole('button', { name: copyButtonName })).toBeTruthy; + + screen.getByRole('button', { name: copyButtonName }).click(); + + expect(mockClipboard).toHaveBeenCalledWith( + 'schemaVersion: 2.1.0\nmetadata:\n name: my-project\n namespace: user-che\n', + ); + + /* 'Copy to clipboard' should be hidden for a while */ + + expect(screen.queryByRole('button', { name: copyButtonName })).toBeFalsy; + + const copyButtonNameAfter = 'Copied'; + expect(screen.queryByRole('button', { name: copyButtonNameAfter })).toBeTruthy; + + /* 'Copy to clipboard' should re-appear after 3000ms */ + + jest.advanceTimersByTime(4000); + expect(screen.queryByRole('button', { name: copyButtonName })).toBeTruthy; + expect(screen.queryByRole('button', { name: copyButtonNameAfter })).toBeFalsy; + }); + }); + + describe('DevWorkspace', () => { + let devWorkspace: devfileApi.DevWorkspace; + + beforeEach(() => { + devWorkspace = { + apiVersion: '1.0.0', + metadata: { + name: 'my-project', + namespace: 'user-che', + labels: {}, + uid: '123', + }, + kind: 'DevWorkspace', + spec: { + template: {}, + started: true, + }, + }; + }); + + test('snapshot', () => { + const snapshot = createSnapshot(devWorkspace); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + test('Cluster Console', () => { + renderComponent(devWorkspace); + + const clusterConsoleButton = screen.getByRole('link', { name: clusterConsole.title }); + + expect(clusterConsoleButton.textContent).toEqual(clusterConsole.title); + }); + }); +}); + +function getComponent(devfileOrDevWorkspace: devfileApi.Devfile | devfileApi.DevWorkspace) { + return ( + + + + ); +} diff --git a/packages/dashboard-frontend/src/components/EditorTools/index.module.css b/packages/dashboard-frontend/src/components/EditorTools/index.module.css index f9925e7b5e..57c9152865 100644 --- a/packages/dashboard-frontend/src/components/EditorTools/index.module.css +++ b/packages/dashboard-frontend/src/components/EditorTools/index.module.css @@ -26,7 +26,7 @@ text-decoration: none; } -.editorTools button { +.editorTools .button { padding: 0; font-size: inherit; outline: none; diff --git a/packages/dashboard-frontend/src/components/EditorTools/index.tsx b/packages/dashboard-frontend/src/components/EditorTools/index.tsx index ea7cc00eaf..488d3e26b1 100644 --- a/packages/dashboard-frontend/src/components/EditorTools/index.tsx +++ b/packages/dashboard-frontend/src/components/EditorTools/index.tsx @@ -10,21 +10,32 @@ * Red Hat, Inc. - initial API and implementation */ +import { ApplicationId, helpers } from '@eclipse-che/common'; import { AlertVariant, Button, Divider, Flex, FlexItem } from '@patternfly/react-core'; -import { CompressIcon, CopyIcon, DownloadIcon, ExpandIcon } from '@patternfly/react-icons'; +import { + CompressIcon, + CopyIcon, + DownloadIcon, + ExpandIcon, + ExternalLinkSquareAltIcon, +} from '@patternfly/react-icons'; import React from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; -import stringify from '../../services/helpers/editor'; -import styles from './index.module.css'; -import devfileApi from '../../services/devfileApi'; -import { AppAlerts } from '../../services/alerts/appAlerts'; +import { ConnectedProps, connect } from 'react-redux'; +import { ToggleBarsContext } from '../../contexts/ToggleBars'; import { lazyInject } from '../../inversify.config'; +import { AppAlerts } from '../../services/alerts/appAlerts'; +import devfileApi, { isDevWorkspace, isDevfileV2 } from '../../services/devfileApi'; +import stringify from '../../services/helpers/editor'; import { AlertItem } from '../../services/helpers/types'; -import { helpers } from '@eclipse-che/common'; -import { ToggleBarsContext } from '../../contexts/ToggleBars'; +import { AppState } from '../../store'; +import { actionCreators } from '../../store/BannerAlert'; +import { selectApplications } from '../../store/ClusterInfo/selectors'; +import styles from './index.module.css'; +import { WorkspaceAdapter } from '../../services/workspace-adapter'; -type Props = { - content: devfileApi.DevWorkspace | devfileApi.Devfile; +type Props = MappedProps & { + devfileOrDevWorkspace: devfileApi.DevWorkspace | devfileApi.Devfile; handleExpand: (isExpand: boolean) => void; }; @@ -32,7 +43,6 @@ type State = { copied?: boolean; isExpanded: boolean; contentText: string; - isWorkspace: boolean; contentBlobUrl: string; }; @@ -51,39 +61,27 @@ class EditorTools extends React.PureComponent { this.state = { isExpanded: false, contentText: '', - isWorkspace: false, contentBlobUrl: '', }; } public componentDidMount(): void { - const { content } = this.props; - try { - const contentText = stringify(content); - const isWorkspace = this.isWorkspace(content); - const contentBlobUrl = URL.createObjectURL( - new Blob([contentText], { type: 'application/x-yaml' }), - ); - this.setState({ contentText, isWorkspace, contentBlobUrl }); - } catch (e) { - this.showAlert({ - key: 'editor-tools-create blob-url-fails', - variant: AlertVariant.danger, - title: helpers.errors.getMessage(e), - }); - } + this.init(); } public componentDidUpdate(): void { - const { content } = this.props; + this.init(); + } + + private init() { + const { devfileOrDevWorkspace } = this.props; try { - const contentText = stringify(content); + const contentText = stringify(devfileOrDevWorkspace); if (contentText !== this.state.contentText) { - const isWorkspace = this.isWorkspace(content); const contentBlobUrl = URL.createObjectURL( new Blob([contentText], { type: 'application/x-yaml' }), ); - this.setState({ contentText, isWorkspace, contentBlobUrl }); + this.setState({ contentText, contentBlobUrl }); } } catch (e) { this.showAlert({ @@ -112,22 +110,6 @@ class EditorTools extends React.PureComponent { } } - private getName(content: devfileApi.DevWorkspace | devfileApi.Devfile): string | undefined { - if ((content as devfileApi.DevWorkspace)?.kind === 'DevWorkspace') { - return (content as devfileApi.DevWorkspace).metadata.name; - } else { - return (content as devfileApi.Devfile).metadata.name; - } - return undefined; - } - - private isWorkspace(content: devfileApi.DevWorkspace | devfileApi.Devfile): boolean { - if ((content as devfileApi.DevWorkspace)?.kind === 'DevWorkspace') { - return true; - } - return false; - } - private onCopyToClipboard(): void { this.setState({ copied: true }); if (this.copiedTimer) { @@ -138,16 +120,58 @@ class EditorTools extends React.PureComponent { }, 3000); } + private buildOpenShiftConsoleItem(): React.ReactElement | undefined { + const { applications, devfileOrDevWorkspace } = this.props; + const clusterConsole = applications.find(app => app.id === ApplicationId.CLUSTER_CONSOLE); + + if (isDevfileV2(devfileOrDevWorkspace)) { + return; + } + const devWorkspace = devfileOrDevWorkspace; + + if (!clusterConsole) { + return; + } + + const devWorkspaceOpenShiftConsoleUrl = WorkspaceAdapter.buildClusterConsoleUrl( + devWorkspace, + clusterConsole.url, + ); + + return ( + <> + + + + + + ); + } + public render(): React.ReactElement { - const { contentText, isWorkspace, contentBlobUrl, isExpanded, copied } = this.state; - const name = this.getName(this.props.content); + const { devfileOrDevWorkspace } = this.props; + const { contentText, contentBlobUrl, isExpanded, copied } = this.state; + + const { name } = devfileOrDevWorkspace.metadata; + + const openshiftConsoleItem = this.buildOpenShiftConsoleItem(); return (
+ {openshiftConsoleItem} this.onCopyToClipboard()}> - @@ -156,7 +180,10 @@ class EditorTools extends React.PureComponent { @@ -165,7 +192,7 @@ class EditorTools extends React.PureComponent { -