Merge pull request #2202 from weaveworks/2154-api-url

Re-factor API URL generation code
This commit is contained in:
Jordan Pellizzari
2017-02-13 09:52:38 -08:00
committed by GitHub
5 changed files with 97 additions and 39 deletions

View File

@@ -681,3 +681,15 @@ export function toggleTroubleshootingMenu(ev) {
type: ActionTypes.TOGGLE_TROUBLESHOOTING_MENU
};
}
export function changeInstance() {
return (dispatch, getState) => {
const state = getState();
getNodesDelta(
getCurrentTopologyUrl(state),
getActiveTopologyOptions(state),
dispatch,
true // forces websocket teardown and reconnect to new instance
);
};
}

View File

@@ -9,7 +9,7 @@ import Term from 'xterm';
import { clickCloseTerminal } from '../actions/app-actions';
import { getNeutralColor } from '../utils/color-utils';
import { setDocumentTitle } from '../utils/title-utils';
import { getPipeStatus, doResizeTty, wsUrl } from '../utils/web-api-utils';
import { getPipeStatus, doResizeTty, getWebsocketUrl, getApiPath } from '../utils/web-api-utils';
const log = debug('scope:terminal');
@@ -101,14 +101,14 @@ class Terminal extends React.Component {
}
createWebsocket(term) {
const socket = new WebSocket(`${wsUrl}/api/pipe/${this.getPipeId()}`);
const socket = new WebSocket(`${getWebsocketUrl()}/api/pipe/${this.getPipeId()}`);
socket.binaryType = 'arraybuffer';
getPipeStatus(this.getPipeId(), this.props.dispatch);
socket.onopen = () => {
clearTimeout(this.reconnectTimeout);
log('socket open to', wsUrl);
log('socket open to', getWebsocketUrl());
this.setState({connected: true});
};
@@ -243,7 +243,7 @@ class Terminal extends React.Component {
const bcr = this.node.getBoundingClientRect();
const minWidth = (this.state.characterWidth * 80) + (8 * 2);
openNewWindow(`terminal.html#!/state/${paramString}`, bcr, minWidth);
openNewWindow(`${getApiPath()}/terminal.html#!/state/${paramString}`, bcr, minWidth);
}
handleResize() {

View File

@@ -1,3 +1,3 @@
exports.reducer = require('./reducers/root').default;
exports.Scope = require('./components/app').default;
exports.ActionTypes = require('./constants/action-types').default;
exports.actions = require('./actions/app-actions');

View File

@@ -1,12 +1,9 @@
import {OrderedMap as makeOrderedMap} from 'immutable';
import { buildOptionsQuery, basePath, getApiPath, getWebsocketUrl } from '../web-api-utils';
describe('WebApiUtils', () => {
const WebApiUtils = require('../web-api-utils');
describe('basePath', () => {
const basePath = WebApiUtils.basePath;
it('should handle /scope/terminal.html', () => {
expect(basePath('/scope/terminal.html')).toBe('/scope');
});
@@ -25,8 +22,6 @@ describe('WebApiUtils', () => {
});
describe('buildOptionsQuery', () => {
const buildOptionsQuery = WebApiUtils.buildOptionsQuery;
it('should handle empty options', () => {
expect(buildOptionsQuery(makeOrderedMap({}))).toBe('');
});
@@ -38,4 +33,55 @@ describe('WebApiUtils', () => {
]))).toBe('foo=2&bar=4');
});
});
describe('getApiPath', () => {
afterEach(() => {
delete process.env.SCOPE_API_PREFIX;
});
it('returns the correct url when running standalone', () => {
expect(getApiPath('/')).toEqual('');
});
it('returns the correct url when running in an iframe', () => {
expect(getApiPath('/api/app/proud-cloud-77')).toEqual('/api/app/proud-cloud-77');
});
it('returns the correct url when running as a component', () => {
process.env.SCOPE_API_PREFIX = '/api';
expect(getApiPath('/app/proud-cloud-77')).toEqual('/api/app/proud-cloud-77');
});
it('returns the correct url from an arbitrary path', () => {
expect(getApiPath('/demo/')).toEqual('/demo');
});
it('returns the correct url from an *.html page', () => {
expect(getApiPath('/contrast.html')).toEqual('');
});
it('returns the correct url from an /*.html page while in an iframe', () => {
expect(getApiPath('/api/app/proud-cloud-77/contrast.html')).toEqual('/api/app/proud-cloud-77');
});
});
describe('getWebsocketUrl', () => {
const host = 'localhost:4042';
afterEach(() => {
delete process.env.SCOPE_API_PREFIX;
});
it('returns the correct url when running standalone', () => {
expect(getWebsocketUrl(host, '/')).toEqual(`ws://${host}`);
});
it('returns the correct url when running in an iframe', () => {
expect(getWebsocketUrl(host, '/api/app/proud-cloud-77')).toEqual(`ws://${host}/api/app/proud-cloud-77`);
});
it('returns the correct url when running as a component', () => {
process.env.SCOPE_API_PREFIX = '/api';
expect(getWebsocketUrl(host, '/app/proud-cloud-77')).toEqual(`ws://${host}/api/app/proud-cloud-77`);
});
it('returns the correct url from an arbitrary path', () => {
expect(getWebsocketUrl(host, '/demo/')).toEqual(`ws://${host}/demo`);
});
it('returns the correct url from an *.html page', () => {
expect(getWebsocketUrl(host, '/contrast.html')).toEqual(`ws://${host}`);
});
it('returns the correct url from an /*.html page while in an iframe', () => {
expect(getWebsocketUrl(host, '/api/app/proud-cloud-77/contrast.html')).toEqual(`ws://${host}/api/app/proud-cloud-77`);
});
});
});

View File

@@ -58,26 +58,18 @@ export function basePathSlash(urlPath) {
return `${basePath(urlPath)}/`;
}
// JJP - `apiPath` is used to get API URLs right when running as a React component.
// This needs to be refactored to just accept a URL prop on the scope component.
let apiPath;
let websocketUrl;
const isIframe = window.location !== window.parent.location;
const isStandalone = window.location.pathname === '/'
|| window.location.pathname === '/demo/'
|| window.location.pathname === '/scoped/'
|| /\/(.+).html/.test(window.location.pathname);
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
export function getApiPath(pathname = window.location.pathname) {
if (process.env.SCOPE_API_PREFIX) {
return basePath(`${process.env.SCOPE_API_PREFIX}${pathname}`);
}
if (isIframe || isStandalone) {
apiPath = 'api';
websocketUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`;
} else {
apiPath = `/api${basePath(window.location.pathname)}/api`;
websocketUrl = `${wsProto}://${location.host}/api${basePath(window.location.pathname)}`;
return basePath(pathname);
}
export const wsUrl = websocketUrl;
export function getWebsocketUrl(host = window.location.host, pathname = window.location.pathname) {
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
return `${wsProto}://${host}${process.env.SCOPE_API_PREFIX || ''}${basePath(pathname)}`;
}
function createWebsocket(topologyUrl, optionsQuery, dispatch) {
if (socket) {
@@ -92,7 +84,7 @@ function createWebsocket(topologyUrl, optionsQuery, dispatch) {
createWebsocketAt = new Date();
firstMessageOnWebsocketAt = 0;
socket = new WebSocket(`${wsUrl}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`);
socket = new WebSocket(`${getWebsocketUrl()}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`);
socket.onopen = () => {
dispatch(openWebsocket());
@@ -154,9 +146,10 @@ export function getAllNodes(getState, dispatch) {
export function getTopologies(options, dispatch) {
clearTimeout(topologyTimer);
const optionsQuery = buildOptionsQuery(options);
const url = `${apiPath}/topology?${optionsQuery}`;
const url = `${getApiPath()}/api/topology?${optionsQuery}`;
reqwest({
url,
type: 'json',
success: (res) => {
dispatch(receiveTopologies(res));
topologyTimer = setTimeout(() => {
@@ -173,11 +166,12 @@ export function getTopologies(options, dispatch) {
});
}
export function getNodesDelta(topologyUrl, options, dispatch) {
export function getNodesDelta(topologyUrl, options, dispatch, forceReload) {
const optionsQuery = buildOptionsQuery(options);
// only recreate websocket if url changed or if forced (weave cloud instance reload);
const isNewUrl = topologyUrl && (topologyUrl !== currentUrl || currentOptions !== optionsQuery);
// only recreate websocket if url changed
if (topologyUrl && (topologyUrl !== currentUrl || currentOptions !== optionsQuery)) {
if (forceReload || isNewUrl) {
createWebsocket(topologyUrl, optionsQuery, dispatch);
currentUrl = topologyUrl;
currentOptions = optionsQuery;
@@ -189,7 +183,7 @@ export function getNodeDetails(topologyUrlsById, currentTopologyId, options, nod
const obj = nodeMap.last();
if (obj && topologyUrlsById.has(obj.topologyId)) {
const topologyUrl = topologyUrlsById.get(obj.topologyId);
let urlComponents = [apiPath, '/', trimStart(topologyUrl, '/api'), '/', encodeURIComponent(obj.id)];
let urlComponents = [getApiPath(), topologyUrl, '/', encodeURIComponent(obj.id)];
if (currentTopologyId === obj.topologyId) {
// Only forward filters for nodes in the current topology
const optionsQuery = buildOptionsQuery(options);
@@ -199,6 +193,7 @@ export function getNodeDetails(topologyUrlsById, currentTopologyId, options, nod
reqwest({
url,
type: 'json',
success: (res) => {
// make sure node is still selected
if (nodeMap.has(res.node.id)) {
@@ -222,9 +217,10 @@ export function getNodeDetails(topologyUrlsById, currentTopologyId, options, nod
export function getApiDetails(dispatch) {
clearTimeout(apiDetailsTimer);
const url = apiPath;
const url = `${getApiPath()}/api`;
reqwest({
url,
type: 'json',
success: (res) => {
dispatch(receiveApiDetails(res));
apiDetailsTimer = setTimeout(() => {
@@ -243,11 +239,12 @@ export function getApiDetails(dispatch) {
export function doControlRequest(nodeId, control, dispatch) {
clearTimeout(controlErrorTimer);
const url = `${apiPath}/control/${encodeURIComponent(control.probeId)}/`
const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
reqwest({
method: 'POST',
url,
type: 'json',
success: (res) => {
dispatch(receiveControlSuccess(nodeId));
if (res) {
@@ -279,12 +276,13 @@ export function doControlRequest(nodeId, control, dispatch) {
export function doResizeTty(pipeId, control, cols, rows) {
const url = `${apiPath}/control/${encodeURIComponent(control.probeId)}/`
const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
return reqwest({
method: 'POST',
url,
type: 'json',
data: JSON.stringify({pipeID: pipeId, width: cols.toString(), height: rows.toString()}),
})
.fail((err) => {
@@ -294,10 +292,11 @@ export function doResizeTty(pipeId, control, cols, rows) {
export function deletePipe(pipeId, dispatch) {
const url = `${apiPath}/pipe/${encodeURIComponent(pipeId)}`;
const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}`;
reqwest({
method: 'DELETE',
url,
type: 'json',
success: () => {
log('Closed the pipe!');
},
@@ -310,10 +309,11 @@ export function deletePipe(pipeId, dispatch) {
export function getPipeStatus(pipeId, dispatch) {
const url = `${apiPath}/pipe/${encodeURIComponent(pipeId)}/check`;
const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}/check`;
reqwest({
method: 'GET',
url,
type: 'json',
complete: (res) => {
const status = {
204: 'PIPE_ALIVE',