diff --git a/license-tool/src/entrypoint.sh b/license-tool/src/entrypoint.sh index 9d69883ec..50dd4d512 100755 --- a/license-tool/src/entrypoint.sh +++ b/license-tool/src/entrypoint.sh @@ -1,12 +1,17 @@ #!/bin/bash -# Validate yarn.lock -if [ -f yarn.lock ]; then - node dash-licenses/yarn/index.js | \ - java -jar dash-licenses/target/org.eclipse.dash.licenses-0.0.1-SNAPSHOT.jar - - echo "The DEPENDENCIES file is being generated..." - node ./bump-deps.js - echo "Done." -else - echo "The yarn.lock file is not present!!!" +if [ ! -f yarn.lock ]; then + echo "Can't find yarn.lock. Generate lock file and try again." + exit 1 fi + +DASH_LICENSES=dash-licenses/core/target/org.eclipse.dash.licenses-0.0.1-SNAPSHOT.jar +if [ ! -f $DASH_LICENSES ]; then + echo "Can't find org.eclipse.dash.licenses-0.0.1-SNAPSHOT.jar. Rebuild 'nodejs-license-tool' image and try again." + exit 1 +fi + +node dash-licenses/yarn/index.js | java -jar $DASH_LICENSES - +echo "The DEPENDENCIES file is being generated..." +node ./bump-deps.js +echo "Done." diff --git a/package.json b/package.json index 66bb429a5..45e60872c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ }, "dependencies": { "@eclipse-che/workspace-client": "^0.0.1-1608729566", - "@patternfly/patternfly": "^4.10.31", "@patternfly/react-core": "~4.84.4", "@patternfly/react-icons": "^4.3.5", "@patternfly/react-table": "^4.5.7", diff --git a/src/Layout/Navigation/__tests__/RecentList.spec.tsx b/src/Layout/Navigation/__tests__/RecentList.spec.tsx index 1fa6b9cd0..439c4290b 100644 --- a/src/Layout/Navigation/__tests__/RecentList.spec.tsx +++ b/src/Layout/Navigation/__tests__/RecentList.spec.tsx @@ -149,6 +149,7 @@ function createFakeStore(workspaces: che.Workspace[]): Store { devfileRegistries: {} as any, user: {} as any, infrastructureNamespace: {} as any, + environment: {} as any, userPreferences: {} as any, }; const middleware = [thunk]; diff --git a/src/containers/IdeLoader.tsx b/src/containers/IdeLoader.tsx index 0a9a7ad67..58644a4c3 100644 --- a/src/containers/IdeLoader.tsx +++ b/src/containers/IdeLoader.tsx @@ -18,8 +18,10 @@ import { RouteComponentProps } from 'react-router'; import { lazyInject } from '../inversify.config'; import IdeLoader, { AlertOptions } from '../pages/IdeLoader'; import { Debounce } from '../services/helpers/debounce'; +import { delay } from '../services/helpers/delay'; import { IdeLoaderTab, WorkspaceStatus } from '../services/helpers/types'; import { AppState } from '../store'; +import { selectIsDevelopment } from '../store/Environment/selectors'; import * as WorkspaceStore from '../store/Workspaces'; import { selectAllWorkspaces, selectLogs, selectWorkspaceById } from '../store/Workspaces/selectors'; @@ -41,7 +43,7 @@ type State = { currentStep: LoadIdeSteps; preselectedTabKey?: IdeLoaderTab; ideUrl?: string; - hasError?: boolean; + hasError: boolean; }; class IdeLoaderContainer extends React.PureComponent { @@ -131,8 +133,7 @@ class IdeLoaderContainer extends React.PureComponent { const workspace = allWorkspaces.find(workspace => workspace.namespace === this.state.namespace && workspace.devfile.metadata.name === this.state.workspaceName); if (workspace && workspace.runtime && workspace.status === WorkspaceStatus[WorkspaceStatus.RUNNING]) { - this.updateIdeUrl(workspace.runtime); - return; + return await this.updateIdeUrl(workspace.runtime); } else if (workspace && workspace.status == WorkspaceStatus[WorkspaceStatus.ERROR]) { this.showErrorAlert(workspace); } @@ -150,21 +151,47 @@ class IdeLoaderContainer extends React.PureComponent { }); } - public async componentDidUpdate(prevProps: Props, prevState: Props): Promise { + public async componentDidUpdate(prevProps: Props, prevState: State): Promise { const { allWorkspaces, match: { params } } = this.props; const { hasError } = this.state; const workspace = allWorkspaces.find(workspace => - workspace.namespace === params.namespace && workspace.devfile.metadata.name === this.workspaceName); - if (workspace && ((prevState.workspaceName === this.workspaceName) && !hasError) && workspace.status === WorkspaceStatus[WorkspaceStatus.ERROR]) { - // When the current workspace didn't have an error but now does then show it - this.showErrorAlert(workspace); - } else if (workspace && (prevState.workspaceName !== this.workspaceName) && workspace.status === WorkspaceStatus[WorkspaceStatus.ERROR]) { - // When the clicked workspace changes and the new one errors then show the new error message - this.setState({ hasError: true, workspaceName: this.workspaceName, currentStep: LoadIdeSteps.START_WORKSPACE, workspaceId: workspace.id }); - this.showErrorAlert(workspace); + workspace.namespace === params.namespace + && workspace.devfile.metadata.name === this.workspaceName); + if (!workspace) { + const alertOptions = { + title: `Workspace "${this.workspaceName}" is not found.`, + alertVariant: AlertVariant.danger, + }; + if (this.loadFactoryPageCallbacks.showAlert) { + this.loadFactoryPageCallbacks.showAlert(alertOptions); + } else { + console.error(alertOptions.title); + } + this.setState({ + hasError: true, + }); + } else if (workspace.status === WorkspaceStatus[WorkspaceStatus.ERROR]) { + if ((prevState.workspaceName === this.workspaceName) && !hasError) { + // When the current workspace didn't have an error but now does then show it + this.showErrorAlert(workspace); + } else if ((prevState.workspaceName !== this.workspaceName)) { + // When the clicked workspace changes and the new one errors then show the new error message + this.setState({ + hasError: true, + workspaceName: this.workspaceName, + currentStep: LoadIdeSteps.START_WORKSPACE, + workspaceId: workspace.id, + }); + this.showErrorAlert(workspace); + } } else if (prevState.workspaceName !== this.workspaceName) { // There is no error in the newly opened workspace so just reset the status back to the initial state - this.setState({ hasError: false, workspaceName: this.workspaceName, currentStep: LoadIdeSteps.INITIALIZING, workspaceId: workspace.id }); + this.setState({ + hasError: false, + workspaceName: this.workspaceName, + currentStep: LoadIdeSteps.INITIALIZING, + workspaceId: workspace.id, + }); } this.debounce.setDelay(1000); } @@ -198,7 +225,7 @@ class IdeLoaderContainer extends React.PureComponent { private async verboseModeHandler(workspace: che.Workspace) { try { await this.props.startWorkspace(workspace.id, { 'debug-workspace-start': true }); - await this.props.deleteWorkspaceLogs(workspace.id); + this.props.deleteWorkspaceLogs(workspace.id); this.setState({ currentStep: LoadIdeSteps.INITIALIZING, hasError: false @@ -223,7 +250,7 @@ class IdeLoaderContainer extends React.PureComponent { } } - private updateIdeUrl(runtime: api.che.workspace.Runtime): void { + private async updateIdeUrl(runtime: api.che.workspace.Runtime): Promise { let ideUrl = ''; const machines = runtime.machines || {}; for (const machineName of Object.keys(machines)) { @@ -240,6 +267,16 @@ class IdeLoaderContainer extends React.PureComponent { this.showAlert('Don\'t know what to open, IDE url is not defined.'); return; } + if (this.props.isDevelopment) { + // workaround to open IDE in iframe while serving dashboard locally + try { + const windowRef = window.open(ideUrl); + await delay(2000); + windowRef?.close(); + } catch (e) { + // noop + } + } this.setState({ currentStep: LoadIdeSteps.OPEN_IDE, ideUrl }); } @@ -254,7 +291,7 @@ class IdeLoaderContainer extends React.PureComponent { const workspace = this.props.allWorkspaces.find(workspace => workspace.id === workspaceId); if (workspace && workspace.runtime) { - this.updateIdeUrl(workspace.runtime); + await this.updateIdeUrl(workspace.runtime); } } @@ -308,7 +345,7 @@ class IdeLoaderContainer extends React.PureComponent { workspaceId={workspaceId || ''} preselectedTabKey={preselectedTabKey} ideUrl={ideUrl} - hasError={hasError === true} + hasError={hasError} workspaceName={workspaceName || ''} callbacks={this.loadFactoryPageCallbacks} /> @@ -320,12 +357,13 @@ class IdeLoaderContainer extends React.PureComponent { const mapStateToProps = (state: AppState) => ({ workspace: selectWorkspaceById(state), allWorkspaces: selectAllWorkspaces(state), - workspacesLogs: selectLogs(state) + workspacesLogs: selectLogs(state), + isDevelopment: selectIsDevelopment(state), }); const connector = connect( mapStateToProps, WorkspaceStore.actionCreators, ); -type MappedProps = ConnectedProps | any; +type MappedProps = ConnectedProps; export default connector(IdeLoaderContainer); diff --git a/src/containers/__tests__/IdeLoader.spec.tsx b/src/containers/__tests__/IdeLoader.spec.tsx index d384b1d32..9e113b46f 100644 --- a/src/containers/__tests__/IdeLoader.spec.tsx +++ b/src/containers/__tests__/IdeLoader.spec.tsx @@ -11,6 +11,7 @@ */ import React from 'react'; +import { Action } from 'redux'; import { Provider } from 'react-redux'; import { AlertActionLink } from '@patternfly/react-core'; import { RenderResult, render, screen } from '@testing-library/react'; @@ -21,13 +22,24 @@ import { createFakeWorkspace } from '../../store/__mocks__/workspace'; import { WorkspaceStatus } from '../../services/helpers/types'; import IdeLoaderContainer, { LoadIdeSteps } from '../IdeLoader'; import { AlertOptions } from '../../pages/IdeLoader'; +import { AppThunk } from '../../store'; +import { ActionCreators } from '../../store/Workspaces'; + +const showAlertMock = jest.fn(); +const requestWorkspaceMock = jest.fn().mockResolvedValue(undefined); +const startWorkspaceMock = jest.fn().mockResolvedValue(undefined); jest.mock('../../store/Workspaces/index', () => { - return { actionCreators: {} }; + return { + actionCreators: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + requestWorkspace: (id: string): AppThunk> => async (): Promise => { requestWorkspaceMock(); }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + startWorkspace: (id: string): AppThunk> => async (): Promise => { startWorkspaceMock(); } + , + } as ActionCreators, + }; }); - -let showAlert = jest.fn(); - jest.mock('../../pages/IdeLoader', () => { return function DummyWizard(props: { hasError: boolean, @@ -40,7 +52,7 @@ jest.mock('../../pages/IdeLoader', () => { } }): React.ReactElement { if (props.callbacks) { - props.callbacks.showAlert = showAlert; + props.callbacks.showAlert = showAlertMock; } return (
Dummy Wizard
{props.hasError.toString()}
@@ -100,41 +112,35 @@ describe('IDE Loader container', () => { const renderComponent = ( namespace: string, workspaceName: string, - startWorkspace: jest.Mock, - requestWorkspace: jest.Mock, ): RenderResult => { const { history, location, match } = getMockRouterProps(ROUTE.IDE_LOADER, { namespace, workspaceName }); return render( - requestWorkspace(id)} - startWorkspace={async (id: string) => startWorkspace(id)} /> + /> , ); }; - beforeEach(() => { - showAlert = jest.fn(); + afterEach(() => { + jest.resetAllMocks(); }); it('should show an error if something wrong', () => { const namespace = 'admin3'; const workspaceName = 'name-wksp-4'; - const requestWorkspace = jest.fn(); - const startWorkspace = jest.fn(); - renderComponent( namespace, workspaceName, - startWorkspace, - requestWorkspace); + ); - expect(startWorkspace).not.toBeCalled(); - expect(requestWorkspace).not.toBeCalled(); - expect(showAlert).toBeCalledWith(expect.objectContaining({ + expect(startWorkspaceMock).not.toBeCalled(); + expect(requestWorkspaceMock).not.toBeCalled(); + expect(showAlertMock).toBeCalledWith(expect.objectContaining({ alertVariant: 'danger', title: 'Failed to find the target workspace.' })); @@ -150,22 +156,18 @@ describe('IDE Loader container', () => { const namespace = 'admin3'; const workspaceName = 'name-wksp-3'; - const requestWorkspace = jest.fn(); - const startWorkspace = jest.fn(); - renderComponent( namespace, workspaceName, - startWorkspace, - requestWorkspace); + ); - expect(startWorkspace).not.toBeCalled(); - expect(requestWorkspace).not.toBeCalled(); + expect(startWorkspaceMock).not.toBeCalled(); + expect(requestWorkspaceMock).not.toBeCalled(); - expect(showAlert).toBeCalledTimes(1); + expect(showAlertMock).toBeCalledTimes(1); const errorAlerts = jest.fn()}>Open in Verbose mode jest.fn()}>Open Logs; - const firstCalledArgs = showAlert.mock.calls[0][0]; + const firstCalledArgs = showAlertMock.mock.calls[0][0]; expect(firstCalledArgs.title).toEqual('Workspace name-wksp-3 failed to start'); expect(firstCalledArgs.alertVariant).toEqual('danger'); expect(JSON.stringify(firstCalledArgs.alertActionLinks)).toEqual(JSON.stringify(errorAlerts)); @@ -182,17 +184,13 @@ describe('IDE Loader container', () => { const workspaceId = 'id-wksp-1'; const workspaceName = 'name-wksp-1'; - const requestWorkspace = jest.fn(); - const startWorkspace = jest.fn(); - renderComponent( namespace, workspaceName, - startWorkspace, - requestWorkspace); + ); - expect(startWorkspace).toHaveBeenCalledTimes(1); - expect(requestWorkspace).not.toBeCalled(); + expect(startWorkspaceMock).toHaveBeenCalledTimes(1); + expect(requestWorkspaceMock).not.toBeCalled(); const elementHasError = screen.getByTestId('ide-loader-has-error'); expect(elementHasError.innerHTML).toEqual('false'); @@ -215,17 +213,13 @@ describe('IDE Loader container', () => { const namespace = 'admin2'; const workspaceName = 'name-wksp-2'; - const requestWorkspace = jest.fn(); - const startWorkspace = jest.fn(); - renderComponent( namespace, workspaceName, - startWorkspace, - requestWorkspace); + ); - expect(startWorkspace).not.toBeCalled(); - expect(requestWorkspace).not.toBeCalled(); + expect(startWorkspaceMock).not.toBeCalled(); + expect(requestWorkspaceMock).not.toBeCalled(); const elementHasError = screen.getByTestId('ide-loader-has-error'); expect(elementHasError.innerHTML).toEqual('false'); diff --git a/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx b/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx index e222e8690..7966edc5f 100644 --- a/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx +++ b/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx @@ -106,6 +106,7 @@ function createFakeStore(metadata?: che.DevfileMetaData[]): Store { }, user: {} as any, infrastructureNamespace: {} as any, + environment: {} as any, userPreferences: {} as any, }; const middleware = [thunk]; diff --git a/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListToolbar.spec.tsx b/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListToolbar.spec.tsx index ddeb2462a..c81a1d342 100644 --- a/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListToolbar.spec.tsx +++ b/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListToolbar.spec.tsx @@ -90,6 +90,7 @@ function createFakeStore(metadata?: che.DevfileMetaData[]): Store { }, user: {} as any, infrastructureNamespace: {} as any, + environment: {} as any, userPreferences: {} as any, }; const middleware = [thunk]; diff --git a/src/pages/GetStarted/GetStartedTab/__tests__/TemporaryStorageSwitch.spec.tsx b/src/pages/GetStarted/GetStartedTab/__tests__/TemporaryStorageSwitch.spec.tsx index 81bc30c62..7b6dfa87f 100644 --- a/src/pages/GetStarted/GetStartedTab/__tests__/TemporaryStorageSwitch.spec.tsx +++ b/src/pages/GetStarted/GetStartedTab/__tests__/TemporaryStorageSwitch.spec.tsx @@ -89,6 +89,7 @@ function createFakeStore(metadata?: che.DevfileMetaData[]): Store { }, user: {} as any, infrastructureNamespace: {} as any, + environment: {} as any, userPreferences: {} as any, }; const middleware = [thunk]; diff --git a/src/pages/GetStarted/__tests__/GetStarted.spec.tsx b/src/pages/GetStarted/__tests__/GetStarted.spec.tsx index 7a8bfc7c3..a6188abba 100644 --- a/src/pages/GetStarted/__tests__/GetStarted.spec.tsx +++ b/src/pages/GetStarted/__tests__/GetStarted.spec.tsx @@ -82,6 +82,7 @@ function createFakeStore(): Store { devfileRegistries: {} as any, user: {} as any, infrastructureNamespace: {} as any, + environment: {} as any, userPreferences: {} as any, }; const middleware = [thunk]; diff --git a/src/pages/IdeLoader/index.tsx b/src/pages/IdeLoader/index.tsx index 904ea1293..bcfa97273 100644 --- a/src/pages/IdeLoader/index.tsx +++ b/src/pages/IdeLoader/index.tsx @@ -38,21 +38,20 @@ import './IdeLoader.styl'; export const SECTION_THEME = PageSectionVariants.light; type Props = { - hasError: boolean, currentStep: LoadIdeSteps, - workspaceName: string; - workspaceId: string; - preselectedTabKey?: IdeLoaderTab + hasError: boolean, ideUrl?: string; + preselectedTabKey?: IdeLoaderTab + workspaceId: string; + workspaceName: string; callbacks?: { showAlert?: (alertOptions: AlertOptions) => void - } + }; }; type State = { ideUrl?: string; workspaceId: string; - loaderVisible: boolean; alertVisible: boolean; activeTabKey: IdeLoaderTab; currentRequestError: string; @@ -70,19 +69,17 @@ export type AlertOptions = { }; class IdeLoader extends React.PureComponent { - private loaderTimer: number; private readonly hideAlert: () => void; private readonly handleTabClick: (event: React.MouseEvent, tabIndex: React.ReactText) => void; public showAlert: (options: AlertOptions) => void; private readonly wizardRef: RefObject; - constructor(props) { + constructor(props: Props) { super(props); this.state = { alertVisible: false, - loaderVisible: false, currentRequestError: '', workspaceId: this.props.workspaceId, activeTabKey: this.props.preselectedTabKey ? this.props.preselectedTabKey : IdeLoaderTab.Progress, @@ -97,8 +94,9 @@ class IdeLoader extends React.PureComponent { this.setState({ alertVisible: false }); } }; + // Init showAlert - let showAlertTimer; + let showAlertTimer: number; this.showAlert = (alertOptions: AlertOptions): void => { this.setState({ currentRequestError: alertOptions.title, currentAlertVariant: alertOptions.alertVariant, alertActionLinks: alertOptions?.alertActionLinks, alertBody: alertOptions?.body }); if (this.state.activeTabKey === IdeLoaderTab.Progress) { @@ -108,7 +106,7 @@ class IdeLoader extends React.PureComponent { if (showAlertTimer) { clearTimeout(showAlertTimer); } - showAlertTimer = setTimeout(() => { + showAlertTimer = window.setTimeout(() => { this.setState({ alertVisible: false }); }, alertOptions.alertVariant === AlertVariant.success ? 2000 : 10000); }; @@ -121,19 +119,7 @@ class IdeLoader extends React.PureComponent { } } - private async handleMessage(event: MessageEvent): Promise { - const { data } = event; - if (data === 'hide-navbar' && this.state.loaderVisible) { - if (this.loaderTimer) { - clearTimeout(this.loaderTimer); - } - await delay(150); - this.setState({ loaderVisible: false }); - } - } - public componentDidMount(): void { - window.addEventListener('message', event => this.handleMessage(event), false); if (this.props.ideUrl) { this.setState({ ideUrl: this.props.ideUrl }); } @@ -142,13 +128,6 @@ class IdeLoader extends React.PureComponent { } } - public componentWillUnmount(): void { - if (this.loaderTimer) { - clearTimeout(this.loaderTimer); - } - window.removeEventListener('message', event => this.handleMessage(event), false); - } - public async componentDidUpdate(): Promise { const { currentStep, hasError, ideUrl, workspaceId } = this.props; @@ -165,55 +144,21 @@ class IdeLoader extends React.PureComponent { this.setState({ workspaceId, alertVisible: false, - loaderVisible: false, }); - if (this.loaderTimer) { - clearTimeout(this.loaderTimer); - } } if (this.state.ideUrl !== ideUrl) { this.setState({ ideUrl }); if (ideUrl) { - this.setState({ loaderVisible: true }); - if (this.loaderTimer) { - clearTimeout(this.loaderTimer); - } - this.loaderTimer = window.setTimeout(() => { - // todo improve this temporary solution for the debugging session - if (window.location.origin.includes('://localhost')) { - window.location.href = ideUrl; - } - if (this.state.loaderVisible) { - this.setState({ loaderVisible: false }); - } - }, 60000); await this.updateIdeIframe(ideUrl, 10); } } } private async updateIdeIframe(url: string, repeat?: number): Promise { - const element = document.getElementById('ide-iframe'); - if (element && element['contentWindow']) { - const keycloak = window['_keycloak'] ? JSON.stringify(window['_keycloak']) : ''; - if (!keycloak) { - element['src'] = url; - return; - } - const doc = element['contentWindow'].document; - doc.open(); - doc.write(` - - - - - - `); - doc.close(); + const iframeElement = document.getElementById('ide-iframe'); + if (iframeElement) { + iframeElement['src'] = url; } else if (repeat) { await delay(500); return this.updateIdeIframe(url, --repeat); @@ -290,18 +235,11 @@ class IdeLoader extends React.PureComponent { public render(): React.ReactElement { const { workspaceName, workspaceId, ideUrl, hasError, currentStep } = this.props; - const { alertVisible, loaderVisible, currentAlertVariant, currentRequestError, alertActionLinks } = this.state; + const { alertVisible, currentAlertVariant, currentRequestError, alertActionLinks } = this.state; if (ideUrl) { return (
- {loaderVisible && ( -
-
- -
-
- )}