CHE-10538: improves workspace loader (#10642)

* CHE-10538: update workspace loading flow

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* update licenses

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* fixup! CHE-10538: update workspace loading flow
6.19.x
Oleksii Kurinnyi 2018-08-07 13:18:10 +03:00 committed by GitHub
parent 0a349dbc4c
commit b03be67f04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 419 additions and 248 deletions

View File

@ -1,8 +1,9 @@
# Copyright (c) 2018-2018 Red Hat, Inc
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
# Copyright (c) 2018-2018 Red Hat, Inc.
# This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v2.0
# which is available at http://www.eclipse.org/legal/epl-2.0.html
#
# SPDX-License-Identifier: EPL-2.0
# This is a Dockerfile allowing to build workspace loader by using a docker container.
# Build step: $ docker build -t eclipse-che-workspace-loader .

View File

@ -1,9 +1,10 @@
<!--
Copyright (c) 2018-2018 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
This program and the accompanying materials
are made available under the terms of the Eclipse Public License v2.0
which is available at http://www.eclipse.org/legal/epl-2.0.html
SPDX-License-Identifier: EPL-2.0
Contributors:
Red Hat, Inc. - initial API and implementation

View File

@ -30,6 +30,7 @@
<div id="workspace-loader-progress-bar"></div>
</div>
</div>
<div id="workspace-loader-reload">Press F5 or click <a href="#">here</a> to try again.</div>
</div>
<div id="workspace-console">
<div id="workspace-console-container"></div>

View File

@ -140,34 +140,15 @@ export class WorkspaceLoader {
this.workspace = workspace;
return this.handleWorkspace();
})
.then(() => this.openIDE())
.catch(err => {
console.error(err);
this.loader.error(err);
this.loader.hideLoader();
this.loader.showReload();
});
}
/**
* Determines whether the workspace has preconfigured IDE.
*/
hasPreconfiguredIDE() : boolean {
if (this.workspace.config.defaultEnv && this.workspace.config.environments) {
let defaultEnvironment = this.workspace.config.defaultEnv;
let environment = this.workspace.config.environments[defaultEnvironment];
let machines = environment.machines;
for (let machineName in machines) {
let servers = machines[machineName].servers;
for (let serverName in servers) {
let attributes = servers[serverName].attributes;
if (attributes['type'] === 'ide') {
return true;
}
}
}
}
return false;
}
/**
* Returns workspace key from current address or empty string when it is undefined.
*/
@ -205,12 +186,30 @@ export class WorkspaceLoader {
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) { return; }
if (xhr.status !== 200) {
reject(xhr.status ? xhr.statusText : "Unknown error");
let response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {}
if (response) {
reject(response);
} else if (xhr.statusText) {
reject(xhr.statusText);
} else {
reject("Unknown error");
}
return;
}
resolve(JSON.parse(xhr.responseText));
};
});
}).catch((error: any) => {
let errorMessage = '';
if (error) {
errorMessage = error.message ? error.message.toString() : error.toString();
}
errorMessage = `Failed to get the workspace` + (errorMessage ? `: "${errorMessage}"` : '.');
return Promise.reject(errorMessage);
});
}
@ -226,12 +225,30 @@ export class WorkspaceLoader {
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) { return; }
if (xhr.status !== 200) {
reject(xhr.status ? xhr.statusText : "Unknown error");
let response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {}
if (response) {
reject(response);
} else if (xhr.statusText) {
reject(xhr.statusText);
} else {
reject("Unknown error");
}
return;
}
resolve(JSON.parse(xhr.responseText));
};
});
}).catch((error: any) => {
let errorMessage = '';
if (error) {
errorMessage = error.message ? error.message.toString() : error.toString();
}
errorMessage = `Failed to start the workspace` + (errorMessage ? `: "${errorMessage}"` : '.');
return Promise.reject(errorMessage);
});
}
@ -240,17 +257,40 @@ export class WorkspaceLoader {
*/
handleWorkspace(): Promise<void> {
if (this.workspace.status === 'RUNNING') {
this.openIDE();
return;
return Promise.resolve();
} else if (this.workspace.status === 'STOPPING') {
this.startAfterStopping = true;
}
return this.subscribeWorkspaceEvents().then(() => {
const masterApiConnectionPromise = new Promise((resolve, reject) => {
if (this.workspace.status === 'STOPPED') {
this.startWorkspace();
} else if (this.workspace.status === 'STOPPING') {
this.startAfterStopping = true;
this.startWorkspace().then(resolve, reject);
} else {
resolve();
}
}).then(() => {
return this.connectMasterApi();
});
const runningOnConnectionPromise = masterApiConnectionPromise
.then((masterApi: CheJsonRpcMasterApi) => {
return new Promise((resolve) => {
masterApi.addListener('open', () => {
this.getWorkspace(this.workspace.id).then((workspace) => {
if (workspace.status === 'RUNNING') {
resolve();
}
});
});
});
});
const runningOnStatusChangePromise = masterApiConnectionPromise
.then((masterApi: CheJsonRpcMasterApi) => {
return this.subscribeWorkspaceEvents(masterApi);
});
return Promise.race([runningOnConnectionPromise, runningOnStatusChangePromise]);
}
/**
@ -262,47 +302,33 @@ export class WorkspaceLoader {
this.loader.log(message);
}
/**
* Handles changing of workspace status.
*
* @param status workspace status
*/
onWorkspaceStatusChanged(status) : void {
if (status === 'RUNNING') {
this.openIDE();
} else if (status === 'STOPPED' && this.startAfterStopping) {
this.startWorkspace();
}
connectMasterApi(): Promise<CheJsonRpcMasterApi> {
return new Promise((resolve, reject) => {
const entryPoint = this.websocketBaseURL() + WEBSOCKET_CONTEXT + this.getAuthenticationToken();
const master = new CheJsonRpcMasterApi(new WebsocketClient(), entryPoint);
master.connect(entryPoint)
.then(() => resolve(master))
.catch((error: any) => reject(error));
});
}
/**
* Subscribes to the workspace events.
*/
subscribeWorkspaceEvents() : Promise<void> {
const websocketClient = new WebsocketClient();
websocketClient.addListener('open', () => {
this.getWorkspace(this.workspace.id).then((workspace) => {
this.onWorkspaceStatusChanged(workspace.status ? workspace.status : '');
});
});
const entryPoint = this.websocketBaseURL() + WEBSOCKET_CONTEXT + this.getAuthenticationToken();
const master = new CheJsonRpcMasterApi(websocketClient, entryPoint);
return new Promise((resolve) => {
master.connect(entryPoint).then(() => {
master.subscribeEnvironmentOutput(this.workspace.id,
(message: any) => this.onEnvironmentOutput(message.text));
master.subscribeWorkspaceStatus(this.workspace.id,
(message: any) => {
subscribeWorkspaceEvents(masterApi: CheJsonRpcMasterApi) : Promise<any> {
return new Promise((resolve, reject) => {
masterApi.subscribeEnvironmentOutput(this.workspace.id,
(message: any) => this.onEnvironmentOutput(message.text));
masterApi.subscribeWorkspaceStatus(this.workspace.id,
(message: any) => {
if (message.error) {
this.loader.error(message.error);
} else {
this.onWorkspaceStatusChanged(message.status);
} else if (message.status === 'RUNNING') {
resolve();
} else if (message.status === 'STOPPED') {
this.startWorkspace().catch((error: any) => reject(error));
}
});
resolve();
});
});
}

View File

@ -11,7 +11,7 @@
*/
'use strict';
import {CheJsonRpcApiClient} from './che-json-rpc-api-service';
import { ICommunicationClient, CODE_REQUEST_TIMEOUT } from './json-rpc-client';
import { ICommunicationClient, CODE_REQUEST_TIMEOUT, CommunicationClientEvent } from './json-rpc-client';
enum MasterChannels {
ENVIRONMENT_OUTPUT = <any>'machine/log',
@ -53,6 +53,14 @@ export class CheJsonRpcMasterApi {
});
}
addListener(eventType: CommunicationClientEvent, handler: Function): void {
this.client.addListener(eventType, handler);
}
removeListener(eventType: CommunicationClientEvent, handler: Function): void {
this.client.removeListener(eventType, handler);
}
onConnectionOpen(): void {
if (this.checkingInterval) {
clearInterval(this.checkingInterval);
@ -113,8 +121,6 @@ export class CheJsonRpcMasterApi {
}
return this.cheJsonRpcApi.connect(entryPoint).then(() => {
return this.fetchClientId();
}).catch((error: any) => {
console.error(`Failed to connect to ${entryPoint}:`, error);
});
}

View File

@ -26,7 +26,18 @@ export class Loader {
}, 1);
/** Add click handler to maximize output */
document.getElementById('workspace-console').onclick = () => this.onclick();
document.getElementById('workspace-console').onclick = () => this.onclickConsole();
document.getElementById('workspace-loader-reload').onclick = () => this.onclickReload();
}
hideLoader(): void {
document.getElementById('workspace-loader-label').style.display = 'none';
document.getElementById('workspace-loader-progress').style.display = 'none';
}
showReload(): void {
document.getElementById('workspace-loader-reload').style.display = 'block';
}
/**
@ -59,7 +70,7 @@ export class Loader {
element.className = "error";
}
onclick(): void {
onclickConsole(): void {
if (document.getElementById('workspace-loader').hasAttribute("max")) {
document.getElementById('workspace-loader').removeAttribute("max");
document.getElementById('workspace-console').removeAttribute("max");
@ -69,4 +80,9 @@ export class Loader {
}
}
onclickReload(): boolean {
window.location.reload();
return false;
}
}

View File

@ -34,6 +34,24 @@
color: #a0a9b7;
}
#workspace-loader-reload {
display: none;
position: absolute;
left: 0px;
top: 0px;
width: 300px;
height: 30px;
font-family: sans-serif;
font-size: 14px;
line-height: 30px;
text-align: center;
color: #a0a9b7;
}
#workspace-loader-reload a {
color: #a0a9b7;
}
#workspace-loader-progress {
position: absolute;
width: 300px;

View File

@ -1,9 +1,10 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which is available at http://www.eclipse.org/legal/epl-2.0.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation

View File

@ -1,9 +1,10 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which is available at http://www.eclipse.org/legal/epl-2.0.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation

View File

@ -1,19 +1,20 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which is available at http://www.eclipse.org/legal/epl-2.0.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
/// <reference path="../src/custom.d.ts" />
/// <reference path="../src/custom.d.ts" />
'use strict';
import {WorkspaceLoader} from '../src/index';
import { WorkspaceLoader } from '../src/index';
import { Loader } from '../src/loader/loader';
describe('Workspace Loader', () => {
@ -28,6 +29,7 @@ describe('Workspace Loader', () => {
<div id="workspace-loader-progress-bar"></div>
</div>
</div>
<div id="workspace-loader-reload">Press F5 or click <a href="#">here</a> to try again.</div>
</div>
<div id="workspace-console">
<div id="workspace-console-container"></div>
@ -64,27 +66,27 @@ describe('Workspace Loader', () => {
} as che.IWorkspace;
});
it('must have "workspace-loader" in DOM', () => {
const loader = document.getElementById('workspace-loader');
expect(loader).toBeTruthy();
it('should have "workspace-loader" in DOM', () => {
const loaderElement = document.getElementById('workspace-loader');
expect(loaderElement).toBeTruthy();
});
it('test when workspace key is not specified', () => {
let loader = new Loader();
let workspaceLoader = new WorkspaceLoader(loader);
it('should not get a workspace if workspace key is not specified', () => {
const loader = new Loader();
const workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey');
spyOn(workspaceLoader, 'getWorkspace');
workspaceLoader.load();
expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled();
expect(workspaceLoader.getWorkspace).not.toHaveBeenCalled();
});
it('test getWorkspace with test value', () => {
let loader = new Loader();
let workspaceLoader = new WorkspaceLoader(loader);
it('should get a workspace by its workspace key', () => {
const loader = new Loader();
const workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
@ -100,64 +102,12 @@ describe('Workspace Loader', () => {
expect(workspaceLoader.getWorkspace).toHaveBeenCalledWith("foo/bar");
});
describe('must open IDE directly when workspace does not have IDE server', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getQueryString').and.returnValue("");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'STOPPED';
fakeWorkspaceConfig.config.environments["default"].machines = {};
fakeWorkspaceConfig.runtime = {};
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callFake(() => {
return new Promise((resolve) => {
resolve();
});
});
spyOn(workspaceLoader, "startWorkspace").and.callFake(() => {
done();
});
spyOn(workspaceLoader, "openIDE");
workspaceLoader.load();
});
it('openIDE must not be called if status is STOPPED', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
it('must subscribe to events', () => {
expect(workspaceLoader.subscribeWorkspaceEvents).toHaveBeenCalled();
});
it('must start the workspace', () => {
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
});
it('must open IDE when workspace become RUNNING', () => {
workspaceLoader.onWorkspaceStatusChanged("RUNNING");
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
describe('must open preconfigured IDE with query parameters', () => {
describe('if workspace has a preconfigured IDE with query parameters', () => {
let ideURL = "ide URL"
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
const loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
@ -166,63 +116,59 @@ describe('Workspace Loader', () => {
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'RUNNING';
fakeWorkspaceConfig.runtime = {machines: {ide: {servers: {server1: {attributes: {type: "ide"}, url: ideURL}}}}};
fakeWorkspaceConfig.runtime = { machines: { ide: { servers: { server1: { attributes: { type: "ide" }, url: ideURL } } } } } as any;
resolve(fakeWorkspaceConfig);
done();
});
});
spyOn(workspaceLoader, "openIDE").and.callThrough();
spyOn(workspaceLoader, "openURL");
workspaceLoader.load();
workspaceLoader.load().then(() => done());
});
it('must be called', () => {
it('should call openURL method with correct parameter', () => {
expect(workspaceLoader.openURL).toHaveBeenCalledWith(ideURL + "?param=value");
});
});
describe('must handle workspace when it has IDE server', () => {
describe('if workspace does not have an IDE server', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
const loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getQueryString').and.returnValue("");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'RUNNING';
fakeWorkspaceConfig.config.environments["default"].machines = {};
fakeWorkspaceConfig.runtime = {} as che.IWorkspaceRuntime;
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "handleWorkspace").and.callFake(() => {
done();
});
workspaceLoader.load();
spyOn(workspaceLoader, "openIDE").and.callThrough();
spyOn(workspaceLoader, "openURL");
workspaceLoader.load().then(() => done());
});
it('basic workspace function must be called', () => {
expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled();
expect(workspaceLoader.getWorkspace).toHaveBeenCalledWith("foo/bar");
});
it('must be called', () => {
expect(workspaceLoader.handleWorkspace).toHaveBeenCalled();
it('should open IDE directly', () => {
expect(workspaceLoader.openURL).toHaveBeenCalledWith(fakeWorkspaceConfig.links.ide);
});
});
describe('must open IDE for RUNNING workspace', () => {
let workspaceLoader;
describe('if workspace is RUNNING', () => {
let workspaceLoader: WorkspaceLoader;
beforeEach((done) => {
let loader = new Loader();
const loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'RUNNING';
@ -230,33 +176,38 @@ describe('Workspace Loader', () => {
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents");
spyOn(workspaceLoader, "openIDE").and.callFake(() => {
done();
});
spyOn(workspaceLoader, "connectMasterApi");
workspaceLoader.load();
spyOn(workspaceLoader, "subscribeWorkspaceEvents");
spyOn(workspaceLoader, "openIDE");
workspaceLoader.load().then(() => done());
});
it('must not subscribe to events', () => {
it('should not connect to workspace master API', () => {
expect(workspaceLoader.connectMasterApi).not.toHaveBeenCalled();
});
it('should not subscribe to workspace events', () => {
expect(workspaceLoader.subscribeWorkspaceEvents).not.toHaveBeenCalled();
});
it('must open IDE immediately', () => {
it('should open IDE immediately', () => {
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
describe('> must start STOPPED workspace', () => {
let workspaceLoader;
describe('if workspace is STOPPED and then starts successfully', () => {
let workspaceLoader: WorkspaceLoader;
let statusChangeCallback: Function;
beforeEach((done) => {
let loader = new Loader();
const loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'STOPPED';
@ -264,48 +215,159 @@ describe('Workspace Loader', () => {
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callFake(() => {
return new Promise((resolve) => {
resolve();
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callThrough();
spyOn(workspaceLoader, "startWorkspace").and.callFake(() => {
done();
return Promise.resolve();
});
spyOn(workspaceLoader, "openIDE");
spyOn(workspaceLoader, "connectMasterApi").and.callFake(() => {
done();
return Promise.resolve({
addListener: () => { },
subscribeEnvironmentOutput: () => { },
subscribeWorkspaceStatus: (workspaceId, callback) => {
statusChangeCallback = callback;
}
});
});
spyOn(workspaceLoader, "openIDE").and.callFake(() => {
return Promise.resolve();
});
workspaceLoader.load();
});
it('openIDE must not be called if status is STOPPED', () => {
it('should not open an IDE', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
it('must subscribe to events', () => {
it('should subscribe to workspace events', () => {
expect(workspaceLoader.subscribeWorkspaceEvents).toHaveBeenCalled();
});
it('must start the workspace', () => {
it('should start the workspace', () => {
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
});
it('openIDE must be called when workspace become RUNNING', () => {
workspaceLoader.onWorkspaceStatusChanged("RUNNING");
expect(workspaceLoader.openIDE).toHaveBeenCalled();
describe('then becomes STARTING', () => {
beforeEach(() => {
statusChangeCallback({ status: 'STARTING' });
})
it('should not open an IDE', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
describe('then becomes RUNNING', () => {
beforeEach(() => {
statusChangeCallback({ status: 'RUNNING' });
});
it('should open an IDE', () => {
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
});
});
describe('must restart STOPPING workspace', () => {
let workspaceLoader;
describe('if workspace is STOPPED and then fails to start', () => {
let workspaceLoader: WorkspaceLoader;
let startPromiseReject: Function;
let workspaceLoadPromise: Promise<void>;
beforeEach((done) => {
let loader = new Loader();
const loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'STOPPED';
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "connectMasterApi").and.callFake(() => {
return Promise.resolve({
addListener: () => { },
subscribeEnvironmentOutput: () => {},
subscribeWorkspaceStatus: () => {}
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callThrough();
spyOn(workspaceLoader, "startWorkspace").and.callFake(() => {
done();
return Promise.reject();
});
spyOn(workspaceLoader, "openIDE").and.callFake(() => {
return Promise.resolve();
});
workspaceLoadPromise = workspaceLoader.load();
});
it('should not open an IDE immediately', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
it('should start the workspace', () => {
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
});
it('should not subscribe to workspace events', () => {
expect(workspaceLoader.subscribeWorkspaceEvents).not.toHaveBeenCalled();
});
it('should not open an IDE', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
describe('then the request for starting the workspace fails', () => {
beforeEach((done) => {
workspaceLoadPromise.then(() => done());
});
it('should hide loader and progress bar', () => {
const workspaceLoaderLabel = document.getElementById('workspace-loader-label'),
workspaceLoaderProgress = document.getElementById('workspace-loader-progress');
expect(workspaceLoaderLabel.style.display).toEqual('none');
expect(workspaceLoaderProgress.style.display).toEqual('none');
});
it('should show message with "try again" prompt', () => {
const workspaceLoaderReload = document.getElementById('workspace-loader-reload');
expect(workspaceLoaderReload).toBeTruthy();
expect(workspaceLoaderReload.style.display).not.toEqual('none');
})
});
});
describe('if workspace is STOPPING', () => {
let workspaceLoader: WorkspaceLoader;
let statusChangeCallback: Function;
beforeEach((done) => {
const loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'STOPPING';
@ -313,35 +375,70 @@ describe('Workspace Loader', () => {
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callFake(() => {
return new Promise((resolve) => {
resolve();
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callThrough()
spyOn(workspaceLoader, "startWorkspace").and.callFake(() => {
return Promise.resolve();
});
spyOn(workspaceLoader, "connectMasterApi").and.callFake(() => {
done();
return Promise.resolve({
addListener: () => { },
subscribeEnvironmentOutput: () => { },
subscribeWorkspaceStatus: (workspaceId, callback) => {
statusChangeCallback = callback;
}
});
});
spyOn(workspaceLoader, "startWorkspace");
spyOn(workspaceLoader, "openIDE");
workspaceLoader.load().then(() => {
done();
spyOn(workspaceLoader, "openIDE").and.callFake(() => {
return Promise.resolve();
});
workspaceLoader.load();
});
it('must start the workspace after stopping', () => {
expect(workspaceLoader.startAfterStopping).toEqual(true);
});
it('must start workspace when workspace status become STOPPED', () => {
workspaceLoader.onWorkspaceStatusChanged("STOPPED");
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
it('should not open an IDE immediately', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
it('must open IDE when workspace become RUNNING', () => {
workspaceLoader.onWorkspaceStatusChanged("RUNNING");
expect(workspaceLoader.openIDE).toHaveBeenCalled();
it('should set flag to restart a workspace', () => {
expect(workspaceLoader.startAfterStopping).toEqual(true);
});
it('should not start a workspace immediately', () => {
expect(workspaceLoader.startWorkspace).not.toHaveBeenCalled();
});
describe('then becomes STOPPED', () => {
beforeEach(() => {
statusChangeCallback({ status: 'STOPPED' });
});
it('should start a workspace', () => {
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
});
it('should not open an IDE', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
describe('then becomes RUNNING', () => {
beforeEach(() => {
statusChangeCallback({ status: 'RUNNING' });
});
it('should open an IDE', () => {
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
});
});
});

View File

@ -1,9 +1,10 @@
/*******************************************************************************
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which is available at http://www.eclipse.org/legal/epl-2.0.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation

View File

@ -1,9 +1,10 @@
/*******************************************************************************
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which is available at http://www.eclipse.org/legal/epl-2.0.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation

View File

@ -1,9 +1,10 @@
/*******************************************************************************
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which is available at http://www.eclipse.org/legal/epl-2.0.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation