mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Move /shorten to /api/shorten_url * better API test assertions * add API documenation * use async await * import dependencies instead of pass in
This commit is contained in:
parent
d5cd01ab1c
commit
7485f4edab
21 changed files with 426 additions and 215 deletions
|
@ -29,8 +29,10 @@ entirely.
|
|||
|
||||
* <<saved-objects-api>>
|
||||
* <<logstash-configuration-management-api>>
|
||||
* <<url-shortening-api>>
|
||||
--
|
||||
|
||||
include::api/saved-objects.asciidoc[]
|
||||
include::api/logstash-configuration-management.asciidoc[]
|
||||
include::api/url-shortening.asciidoc[]
|
||||
|
||||
|
|
11
docs/api/url-shortening.asciidoc
Normal file
11
docs/api/url-shortening.asciidoc
Normal file
|
@ -0,0 +1,11 @@
|
|||
[[url-shortening-api]]
|
||||
== URL Shortening API
|
||||
|
||||
Kibana URLs contain the state of the application making them very long and cumbersome.
|
||||
Internet Explorer has URL length restrictions, and some wiki and markup parsers don't do well with the full-length version of the Kibana URL.
|
||||
|
||||
The short URLs enabled by this API are designed to make sharing Kibana URLs easier.
|
||||
|
||||
* <<url-shortening-api-api-shorten-url>>
|
||||
|
||||
include::url_shortening/shorten_url.asciidoc[]
|
46
docs/api/url_shortening/shorten_url.asciidoc
Normal file
46
docs/api/url_shortening/shorten_url.asciidoc
Normal file
|
@ -0,0 +1,46 @@
|
|||
[[url-shortening-api-api-shorten-url]]
|
||||
=== Shorten URL
|
||||
|
||||
The Shorten URL API allows for converting a Kibana URL into a token.
|
||||
|
||||
==== Request
|
||||
|
||||
`POST /api/shorten_url`
|
||||
|
||||
==== Request Body
|
||||
|
||||
The request body must be a JSON object containing the following properties:
|
||||
|
||||
`url` (required)::
|
||||
(string) Kibana URL, relative to `/app/kibana`, to be shortened.
|
||||
|
||||
==== Response body
|
||||
|
||||
The response body will have a top level `urlId` property that contains
|
||||
the shortened URL token for the provided request body.
|
||||
|
||||
==== Examples
|
||||
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
POST api/shorten_url
|
||||
{
|
||||
"url": "/app/kibana#/dashboard?_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(darkTheme:!f,hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:'1',w:24,x:0,y:0),id:'8f4d0c00-4c86-11e8-b3d7-01146121b73d',panelIndex:'1',type:visualization,version:'7.0.0-alpha1')),query:(language:lucene,query:''),timeRestore:!f,title:'New%20Dashboard',viewMode:edit)",
|
||||
}
|
||||
--------------------------------------------------
|
||||
// KIBANA
|
||||
|
||||
A successful call returns a response code of `200` and a response body
|
||||
containing a JSON structure similar to the following example:
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
"urlId": "f73b295ff92718b26bc94edac766d8e3"
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
||||
A shortened Kibana URL can then be constructed for easier sharing.
|
||||
`http://localhost:5601/goto/f73b295ff92718b26bc94edac766d8e3`
|
||||
|
|
@ -24,9 +24,6 @@ import Boom from 'boom';
|
|||
import Hapi from 'hapi';
|
||||
import getDefaultRoute from './get_default_route';
|
||||
import { setupVersionCheck } from './version_check';
|
||||
import { handleShortUrlError } from './short_url_error';
|
||||
import { shortUrlAssertValid } from './short_url_assert_valid';
|
||||
import { shortUrlLookupProvider } from './short_url_lookup';
|
||||
import { registerHapiPlugins } from './register_hapi_plugins';
|
||||
import { setupXsrf } from './xsrf';
|
||||
|
||||
|
@ -34,8 +31,6 @@ export default async function (kbnServer, server, config) {
|
|||
kbnServer.server = new Hapi.Server();
|
||||
server = kbnServer.server;
|
||||
|
||||
const shortUrlLookup = shortUrlLookupProvider(server);
|
||||
|
||||
// Note that all connection options configured here should be exactly the same
|
||||
// as in `getServerOptions()` in the new platform (see `src/core/server/http/http_tools`).
|
||||
//
|
||||
|
@ -144,45 +139,6 @@ export default async function (kbnServer, server, config) {
|
|||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/goto/{urlId}',
|
||||
handler: async function (request, reply) {
|
||||
try {
|
||||
const url = await shortUrlLookup.getUrl(request.params.urlId, request);
|
||||
shortUrlAssertValid(url);
|
||||
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage');
|
||||
if (!stateStoreInSessionStorage) {
|
||||
reply().redirect(config.get('server.basePath') + url);
|
||||
return;
|
||||
}
|
||||
|
||||
const app = server.getHiddenUiAppById('stateSessionStorageRedirect');
|
||||
reply.renderApp(app, {
|
||||
redirectUrl: url,
|
||||
});
|
||||
} catch (err) {
|
||||
reply(handleShortUrlError(err));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/shorten',
|
||||
handler: async function (request, reply) {
|
||||
try {
|
||||
shortUrlAssertValid(request.payload.url);
|
||||
const urlId = await shortUrlLookup.generateUrlId(request.payload.url, request);
|
||||
reply(urlId);
|
||||
} catch (err) {
|
||||
reply(handleShortUrlError(err));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Expose static assets (fonts, favicons).
|
||||
server.exposeStaticDir('/ui/fonts/{path*}', resolve(__dirname, '../../ui/public/assets/fonts'));
|
||||
server.exposeStaticDir('/ui/favicons/{path*}', resolve(__dirname, '../../ui/public/assets/favicons'));
|
||||
|
|
|
@ -36,6 +36,7 @@ import * as Plugins from './plugins';
|
|||
import { indexPatternsMixin } from './index_patterns';
|
||||
import { savedObjectsMixin } from './saved_objects';
|
||||
import { sampleDataMixin } from './sample_data';
|
||||
import { urlShorteningMixin } from './url_shortening';
|
||||
import { kibanaIndexMappingsMixin } from './mappings';
|
||||
import { serverExtensionsMixin } from './server_extensions';
|
||||
import { uiMixin } from '../ui';
|
||||
|
@ -93,6 +94,9 @@ export default class KbnServer {
|
|||
// setup routes for installing/uninstalling sample data sets
|
||||
sampleDataMixin,
|
||||
|
||||
// setup routes for short urls
|
||||
urlShorteningMixin,
|
||||
|
||||
// ensure that all bundles are built, or that the
|
||||
// watch bundle server is running
|
||||
optimizeMixin,
|
||||
|
|
20
src/server/url_shortening/index.js
Normal file
20
src/server/url_shortening/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { urlShorteningMixin } from './url_shortening_mixin';
|
50
src/server/url_shortening/routes/create_routes.js
Normal file
50
src/server/url_shortening/routes/create_routes.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { handleShortUrlError } from './lib/short_url_error';
|
||||
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
|
||||
import { shortUrlLookupProvider } from './lib/short_url_lookup';
|
||||
import { createGotoRoute } from './goto';
|
||||
import { createShortenUrlRoute } from './shorten_url';
|
||||
|
||||
|
||||
export function createRoutes(server, config) {
|
||||
const shortUrlLookup = shortUrlLookupProvider(server);
|
||||
|
||||
server.route(createGotoRoute({ server, config, shortUrlLookup }));
|
||||
server.route(createShortenUrlRoute({ shortUrlLookup }));
|
||||
|
||||
// TODO remove deprecated '/shorten' API in master (7.0)
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/shorten',
|
||||
handler: async function (request, reply) {
|
||||
server.log(
|
||||
['warning', 'deprecation'],
|
||||
`'/shorten' API has been deprecated and will be removed in 7.0, use the '/api/shorten_url' API instead`);
|
||||
try {
|
||||
shortUrlAssertValid(request.payload.url);
|
||||
const urlId = await shortUrlLookup.generateUrlId(request.payload.url, request);
|
||||
reply(urlId);
|
||||
} catch (err) {
|
||||
reply(handleShortUrlError(err));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
46
src/server/url_shortening/routes/goto.js
Normal file
46
src/server/url_shortening/routes/goto.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { handleShortUrlError } from './lib/short_url_error';
|
||||
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
|
||||
|
||||
export const createGotoRoute = ({ server, config, shortUrlLookup }) => ({
|
||||
method: 'GET',
|
||||
path: '/goto/{urlId}',
|
||||
handler: async function (request, reply) {
|
||||
try {
|
||||
const url = await shortUrlLookup.getUrl(request.params.urlId, request);
|
||||
shortUrlAssertValid(url);
|
||||
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage');
|
||||
if (!stateStoreInSessionStorage) {
|
||||
reply().redirect(config.get('server.basePath') + url);
|
||||
return;
|
||||
}
|
||||
|
||||
const app = server.getHiddenUiAppById('stateSessionStorageRedirect');
|
||||
reply.renderApp(app, {
|
||||
redirectUrl: url,
|
||||
});
|
||||
} catch (err) {
|
||||
reply(handleShortUrlError(err));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import sinon from 'sinon';
|
||||
import { shortUrlLookupProvider } from './short_url_lookup';
|
||||
import { SavedObjectsClient } from '../saved_objects';
|
||||
import { SavedObjectsClient } from '../../../saved_objects';
|
||||
|
||||
describe('shortUrlLookupProvider', () => {
|
||||
const ID = 'bf00ad16941fc51420f91a93428b27a0';
|
35
src/server/url_shortening/routes/shorten_url.js
Normal file
35
src/server/url_shortening/routes/shorten_url.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { handleShortUrlError } from './lib/short_url_error';
|
||||
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
|
||||
|
||||
export const createShortenUrlRoute = ({ shortUrlLookup }) => ({
|
||||
method: 'POST',
|
||||
path: '/api/shorten_url',
|
||||
handler: async function (request, reply) {
|
||||
try {
|
||||
shortUrlAssertValid(request.payload.url);
|
||||
const urlId = await shortUrlLookup.generateUrlId(request.payload.url, request);
|
||||
reply({ urlId });
|
||||
} catch (err) {
|
||||
reply(handleShortUrlError(err));
|
||||
}
|
||||
}
|
||||
});
|
23
src/server/url_shortening/url_shortening_mixin.js
Normal file
23
src/server/url_shortening/url_shortening_mixin.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { createRoutes } from './routes/create_routes';
|
||||
|
||||
export function urlShorteningMixin(kbnServer, server, config) {
|
||||
createRoutes(server, config);
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import chrome from '../../chrome';
|
||||
import { UrlShortenerProvider } from '../lib/url_shortener';
|
||||
|
||||
describe('Url shortener', () => {
|
||||
let urlShortener;
|
||||
let $httpBackend;
|
||||
const shareId = 'id123';
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (_$rootScope_, _$location_, _$httpBackend_, Private) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
urlShortener = Private(UrlShortenerProvider);
|
||||
}));
|
||||
|
||||
describe('Shorten without base path', () => {
|
||||
it('should shorten urls with a port', function (done) {
|
||||
$httpBackend.when('POST', '/shorten').respond(function (type, route, data) {
|
||||
expect(JSON.parse(data).url).to.be('/app/kibana#123');
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl('http://localhost:5601/app/kibana#123').then(function (url) {
|
||||
expect(url).to.be(`http://localhost:5601/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', function (done) {
|
||||
$httpBackend.when('POST', '/shorten').respond(function (type, route, data) {
|
||||
expect(JSON.parse(data).url).to.be('/app/kibana#123');
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl('http://localhost/app/kibana#123').then(function (url) {
|
||||
expect(url).to.be(`http://localhost/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shorten with base path', () => {
|
||||
const basePath = '/foo';
|
||||
|
||||
let getBasePath;
|
||||
beforeEach(ngMock.inject((Private) => {
|
||||
getBasePath = sinon.stub(chrome, 'getBasePath').returns(basePath);
|
||||
urlShortener = Private(UrlShortenerProvider);
|
||||
}));
|
||||
|
||||
it('should shorten urls with a port', (done) => {
|
||||
$httpBackend.when('POST', `${basePath}/shorten`).respond((type, route, data) => {
|
||||
expect(JSON.parse(data).url).to.be('/app/kibana#123');
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl(`http://localhost:5601${basePath}/app/kibana#123`).then((url) => {
|
||||
expect(url).to.be(`http://localhost:5601${basePath}/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', (done) => {
|
||||
$httpBackend.when('POST', `${basePath}/shorten`).respond((type, route, data) => {
|
||||
expect(JSON.parse(data).url).to.be('/app/kibana#123');
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl(`http://localhost${basePath}/app/kibana#123`).then((url) => {
|
||||
expect(url).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string', (done) => {
|
||||
$httpBackend.when('POST', `${basePath}/shorten`).respond((type, route, data) => {
|
||||
expect(JSON.parse(data).url).to.be('/app/kibana?foo#123');
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl(`http://localhost${basePath}/app/kibana?foo#123`).then((url) => {
|
||||
expect(url).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should shorten urls without a hash', (done) => {
|
||||
$httpBackend.when('POST', `${basePath}/shorten`).respond((type, route, data) => {
|
||||
expect(JSON.parse(data).url).to.be('/app/kibana');
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl(`http://localhost${basePath}/app/kibana`).then((url) => {
|
||||
expect(url).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string in the hash', (done) => {
|
||||
const relativeUrl = "/app/kibana#/discover?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"; //eslint-disable-line max-len, quotes
|
||||
$httpBackend.when('POST', `${basePath}/shorten`).respond((type, route, data) => {
|
||||
expect(JSON.parse(data).url).to.be(relativeUrl);
|
||||
return [200, shareId];
|
||||
});
|
||||
urlShortener.shortenUrl(`http://localhost${basePath}${relativeUrl}`).then((url) => {
|
||||
expect(url).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
done();
|
||||
});
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getBasePath.restore();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -28,7 +28,7 @@ import {
|
|||
} from '../../state_management/state_hashing';
|
||||
import { toastNotifications } from '../../notify';
|
||||
|
||||
import { UrlShortenerProvider } from '../lib/url_shortener';
|
||||
import { shortenUrl } from '../lib/url_shortener';
|
||||
|
||||
import { uiModules } from '../../modules';
|
||||
import shareTemplate from '../views/share.html';
|
||||
|
@ -36,7 +36,6 @@ const app = uiModules.get('kibana');
|
|||
|
||||
app.directive('share', function (Private) {
|
||||
const getUnhashableStates = Private(getUnhashableStatesProvider);
|
||||
const urlShortener = Private(UrlShortenerProvider);
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
|
@ -144,9 +143,12 @@ app.directive('share', function (Private) {
|
|||
this.urlFlags.shortSnapshot = !this.urlFlags.shortSnapshot;
|
||||
|
||||
if (this.urlFlags.shortSnapshot) {
|
||||
urlShortener.shortenUrl(this.urls.snapshot)
|
||||
shortenUrl(this.urls.snapshot)
|
||||
.then(shortUrl => {
|
||||
this.urls.shortSnapshot = shortUrl;
|
||||
// We're using ES6 Promises, not $q, so we have to wrap this in $apply.
|
||||
$scope.$apply(() => {
|
||||
this.urls.shortSnapshot = shortUrl;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -156,9 +158,12 @@ app.directive('share', function (Private) {
|
|||
|
||||
if (this.urlFlags.shortSnapshotIframe) {
|
||||
const snapshotIframe = this.makeUrlEmbeddable(this.urls.snapshot);
|
||||
urlShortener.shortenUrl(snapshotIframe)
|
||||
shortenUrl(snapshotIframe)
|
||||
.then(shortUrl => {
|
||||
this.urls.shortSnapshotIframe = shortUrl;
|
||||
// We're using ES6 Promises, not $q, so we have to wrap this in $apply.
|
||||
$scope.$apply(() => {
|
||||
this.urls.shortSnapshotIframe = shortUrl;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,34 +19,30 @@
|
|||
|
||||
import chrome from '../../chrome';
|
||||
import url from 'url';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
export function UrlShortenerProvider(Notifier, $http) {
|
||||
const notify = new Notifier({
|
||||
location: 'Url Shortener'
|
||||
});
|
||||
export async function shortenUrl(absoluteUrl) {
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
function shortenUrl(absoluteUrl) {
|
||||
const basePath = chrome.getBasePath();
|
||||
const parsedUrl = url.parse(absoluteUrl);
|
||||
const path = parsedUrl.path.replace(basePath, '');
|
||||
const hash = parsedUrl.hash ? parsedUrl.hash : '';
|
||||
const relativeUrl = path + hash;
|
||||
|
||||
const parsedUrl = url.parse(absoluteUrl);
|
||||
const path = parsedUrl.path.replace(basePath, '');
|
||||
const hash = parsedUrl.hash ? parsedUrl.hash : '';
|
||||
const relativeUrl = path + hash;
|
||||
const body = JSON.stringify({ url: relativeUrl });
|
||||
|
||||
const formData = { url: relativeUrl };
|
||||
|
||||
return $http.post(`${basePath}/shorten`, formData).then((result) => {
|
||||
return url.format({
|
||||
protocol: parsedUrl.protocol,
|
||||
host: parsedUrl.host,
|
||||
pathname: `${basePath}/goto/${result.data}`
|
||||
});
|
||||
}).catch((response) => {
|
||||
notify.error(response);
|
||||
try {
|
||||
const resp = await kfetch({ method: 'POST', 'pathname': '/api/shorten_url', body });
|
||||
return url.format({
|
||||
protocol: parsedUrl.protocol,
|
||||
host: parsedUrl.host,
|
||||
pathname: `${basePath}/goto/${resp.urlId}`
|
||||
});
|
||||
} catch (fetchError) {
|
||||
toastNotifications.addDanger({
|
||||
title: `Unable to create short URL. Error: ${fetchError.message}`,
|
||||
'data-test-subj': 'shortenUrlFailure',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
shortenUrl
|
||||
};
|
||||
}
|
||||
|
|
140
src/ui/public/share/lib/url_shortener.test.js
Normal file
140
src/ui/public/share/lib/url_shortener.test.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
jest.mock('ui/kfetch', () => ({}));
|
||||
|
||||
jest.mock('../../chrome', () => ({}));
|
||||
|
||||
jest.mock('ui/notify',
|
||||
() => ({
|
||||
toastNotifications: {
|
||||
addDanger: () => {},
|
||||
}
|
||||
}), { virtual: true });
|
||||
|
||||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import { shortenUrl } from './url_shortener';
|
||||
|
||||
describe('Url shortener', () => {
|
||||
const shareId = 'id123';
|
||||
|
||||
let kfetchStub;
|
||||
beforeEach(() => {
|
||||
kfetchStub = sinon.stub();
|
||||
require('ui/kfetch').kfetch = async (...args) => {
|
||||
return kfetchStub(...args);
|
||||
};
|
||||
});
|
||||
|
||||
describe('Shorten without base path', () => {
|
||||
beforeAll(() => {
|
||||
require('../../chrome').getBasePath = () => {
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
it('should shorten urls with a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl('http://localhost:5601/app/kibana#123');
|
||||
expect(shortUrl).to.be(`http://localhost:5601/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl('http://localhost/app/kibana#123');
|
||||
expect(shortUrl).to.be(`http://localhost/goto/${shareId}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shorten with base path', () => {
|
||||
const basePath = '/foo';
|
||||
|
||||
beforeAll(() => {
|
||||
require('../../chrome').getBasePath = () => {
|
||||
return basePath;
|
||||
};
|
||||
});
|
||||
|
||||
it('should shorten urls with a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost:5601${basePath}/app/kibana#123`);
|
||||
expect(shortUrl).to.be(`http://localhost:5601${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls without a port', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana#123`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana?foo#123"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana?foo#123`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls without a hash', async () => {
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana"}'
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}/app/kibana`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
|
||||
it('should shorten urls with a query string in the hash', async () => {
|
||||
const relativeUrl = "/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"; //eslint-disable-line max-len, quotes
|
||||
kfetchStub.withArgs({
|
||||
method: 'POST',
|
||||
pathname: `/api/shorten_url`,
|
||||
body: '{"url":"/app/kibana#/discover?_g=(refreshInterval:(pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(_source),index:%27logstash-*%27,interval:auto,query:(query_string:(analyze_wildcard:!t,query:%27*%27)),sort:!(%27@timestamp%27,desc))"}' //eslint-disable-line max-len, quotes
|
||||
}).returns(Promise.resolve({ urlId: shareId }));
|
||||
|
||||
const shortUrl = await shortenUrl(`http://localhost${basePath}${relativeUrl}`);
|
||||
expect(shortUrl).to.be(`http://localhost${basePath}/goto/${shareId}`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -27,7 +27,8 @@ export default function ({ getService }) {
|
|||
before(() => esArchiver.load('saved_objects/basic'));
|
||||
after(() => esArchiver.unload('saved_objects/basic'));
|
||||
|
||||
it('generates shortened urls', async () => {
|
||||
// TODO remove deprecated '/shorten' API in master (7.0)
|
||||
it('generates shortened urls with deprecated URL', async () => {
|
||||
const resp = await supertest
|
||||
.post('/shorten')
|
||||
.set('content-type', 'application/json')
|
||||
|
@ -38,14 +39,27 @@ export default function ({ getService }) {
|
|||
expect(resp.text.length > 0).to.be(true);
|
||||
});
|
||||
|
||||
it('generates shortened urls', async () => {
|
||||
const resp = await supertest
|
||||
.post('/api/shorten_url')
|
||||
.set('content-type', 'application/json')
|
||||
.send({ url: '/app/kibana#/visualize/create' })
|
||||
.expect(200);
|
||||
|
||||
expect(resp.body).to.have.property('urlId');
|
||||
expect(typeof resp.body.urlId).to.be('string');
|
||||
expect(resp.body.urlId.length > 0).to.be(true);
|
||||
});
|
||||
|
||||
it('redirects shortened urls', async () => {
|
||||
const resp = await supertest
|
||||
.post('/shorten')
|
||||
.post('/api/shorten_url')
|
||||
.set('content-type', 'application/json')
|
||||
.send({ url: '/app/kibana#/visualize/create' });
|
||||
|
||||
const urlId = resp.body.urlId;
|
||||
await supertest
|
||||
.get(`/goto/${resp.text}`)
|
||||
.get(`/goto/${urlId}`)
|
||||
.expect(302)
|
||||
.expect('location', '/app/kibana#/visualize/create');
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue