Skip to content

Commit

Permalink
Merge pull request #5497 from BigFunger/url-shortener
Browse files Browse the repository at this point in the history
Url shortener
  • Loading branch information
BigFunger committed Dec 18, 2015
2 parents c31bff2 + 96b7404 commit aad699f
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 62 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"bootstrap": "3.3.5",
"brace": "0.5.1",
"bunyan": "1.4.0",
"clipboard": "1.5.5",
"commander": "2.8.1",
"css-loader": "0.17.0",
"d3": "3.5.6",
Expand Down
11 changes: 2 additions & 9 deletions src/plugins/kibana/public/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ define(function (require) {
require('ui/config');
require('ui/notify');
require('ui/typeahead');
require('ui/share');

require('plugins/kibana/dashboard/directives/grid');
require('plugins/kibana/dashboard/components/panel/panel');
Expand Down Expand Up @@ -233,15 +234,7 @@ define(function (require) {
ui: $state.options,
save: $scope.save,
addVis: $scope.addVis,
addSearch: $scope.addSearch,
shareData: function () {
return {
link: $location.absUrl(),
// This sucks, but seems like the cleanest way. Uhg.
embed: '<iframe src="' + $location.absUrl().replace('?', '?embed&') +
'" height="600" width="800"></iframe>'
};
}
addSearch: $scope.addSearch
};

init();
Expand Down
25 changes: 4 additions & 21 deletions src/plugins/kibana/public/dashboard/partials/share.html
Original file line number Diff line number Diff line change
@@ -1,21 +1,4 @@
<form role="form" class="vis-share">

<p>
<div class="input-group">
<label>
Embed this dashboard
<small>Add to your html source. Note all clients must still be able to access kibana</small>
</label>
<div class="form-control" disabled>{{opts.shareData().embed}}</div>
</div>
</p>

<p>
<div class="input-group">
<label>
Share a link
</label>
<div class="form-control" disabled>{{opts.shareData().link}}</div>
</div>
</p>
</form>
<share
object-type="dashboard"
object-id="{{opts.dashboard.id}}">
</share>
4 changes: 3 additions & 1 deletion src/plugins/kibana/public/discover/controllers/discover.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ define(function (require) {
require('ui/state_management/app_state');
require('ui/timefilter');
require('ui/highlight/highlight_tags');
require('ui/share');

var app = require('ui/modules').get('apps/discover', [
'kibana/notify',
Expand Down Expand Up @@ -91,7 +92,8 @@ define(function (require) {
// config panel templates
$scope.configTemplate = new ConfigTemplate({
load: require('plugins/kibana/discover/partials/load_search.html'),
save: require('plugins/kibana/discover/partials/save_search.html')
save: require('plugins/kibana/discover/partials/save_search.html'),
share: require('plugins/kibana/discover/partials/share_search.html')
});

$scope.timefilter = timefilter;
Expand Down
10 changes: 10 additions & 0 deletions src/plugins/kibana/public/discover/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@
<i aria-hidden="true" class="fa fa-folder-open-o"></i>
</button>
</kbn-tooltip>
<kbn-tooltip text="Share" placement="bottom" append-to-body="1">
<button
aria-label="Share Search"
aria-haspopup="true"
aria-expanded="{{ configTemplate.is('share') }}"
ng-class="{active: configTemplate.is('share')}"
ng-click="configTemplate.toggle('share');">
<i aria-hidden="true" class="fa fa-external-link"></i>
</button>
</kbn-tooltip>
</div>
</navbar>

Expand Down
5 changes: 5 additions & 0 deletions src/plugins/kibana/public/discover/partials/share_search.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<share
object-type="search"
object-id="{{opts.savedSearch.id}}"
allow-embed="false">
</share>
10 changes: 1 addition & 9 deletions src/plugins/kibana/public/visualize/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ define(function (require) {

require('ui/visualize');
require('ui/collapsible_sidebar');
require('ui/share');

require('ui/routes')
.when('/visualize/create', {
Expand Down Expand Up @@ -234,15 +235,6 @@ define(function (require) {
}, notify.fatal);
};

$scope.shareData = function () {
return {
link: $location.absUrl(),
// This sucks, but seems like the cleanest way. Uhg.
embed: '<iframe src="' + $location.absUrl().replace('?', '?embed&') +
'" height="600" width="800"></iframe>'
};
};

$scope.unlink = function () {
if (!$state.linked) return;

Expand Down
26 changes: 4 additions & 22 deletions src/plugins/kibana/public/visualize/editor/panels/share.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,4 @@
<form role="form" class="vis-share">

<p>
<div class="form-group">
<label>
Embed this visualization.
<small>Add to your html source. Note all clients must still be able to access kibana</small>
</label>
<div class="form-control" disabled>{{conf.shareData().embed}}</div>
</div>
</p>

<p>
<div class="form-group">
<label>
Share a link
</label>
<div class="form-control" disabled>{{conf.shareData().link}}</div>
</div>
</p>

</form>
<share
object-type="visualization"
object-id="{{conf.savedVis.id}}">
</share>
20 changes: 20 additions & 0 deletions src/server/http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ module.exports = function (kbnServer, server, config) {

server = kbnServer.server = new Hapi.Server();

const shortUrlLookup = require('./short_url_lookup')(server);

// Create a new connection
var connectionOptions = {
host: config.get('server.host'),
Expand Down Expand Up @@ -154,5 +156,23 @@ module.exports = function (kbnServer, server, config) {
}
});

server.route({
method: 'GET',
path: '/goto/{urlId}',
handler: async function (request, reply) {
const url = await shortUrlLookup.getUrl(request.params.urlId);
reply().redirect(url);
}
});

server.route({
method: 'POST',
path: '/shorten',
handler: async function (request, reply) {
const urlId = await shortUrlLookup.generateUrlId(request.payload.url);
reply(urlId);
}
});

return kbnServer.mixin(require('./xsrf'));
};
101 changes: 101 additions & 0 deletions src/server/http/short_url_lookup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const crypto = require('crypto');

export default function (server) {
async function updateMetadata(urlId, urlDoc) {
const client = server.plugins.elasticsearch.client;

try {
await client.update({
index: '.kibana',
type: 'url',
id: urlId,
body: {
doc: {
'accessDate': new Date(),
'accessCount': urlDoc._source.accessCount + 1
}
}
});
} catch (err) {
server.log('Warning: Error updating url metadata', err);
//swallow errors. It isn't critical if there is no update.
}
}

async function getUrlDoc(urlId) {
const urlDoc = await new Promise((resolve, reject) => {
const client = server.plugins.elasticsearch.client;

client.get({
index: '.kibana',
type: 'url',
id: urlId
})
.then(response => {
resolve(response);
})
.catch(err => {
resolve();
});
});

return urlDoc;
}

async function createUrlDoc(url, urlId) {
const newUrlId = await new Promise((resolve, reject) => {
const client = server.plugins.elasticsearch.client;

client.index({
index: '.kibana',
type: 'url',
id: urlId,
body: {
url,
'accessCount': 0,
'createDate': new Date(),
'accessDate': new Date()
}
})
.then(response => {
resolve(response._id);
})
.catch(err => {
reject(err);
});
});

return newUrlId;
}

function createUrlId(url) {
const urlId = crypto.createHash('md5')
.update(url)
.digest('hex');

return urlId;
}

return {
async generateUrlId(url) {
const urlId = createUrlId(url);

const urlDoc = await getUrlDoc(urlId);
if (urlDoc) return urlId;

return createUrlDoc(url, urlId);
},
async getUrl(urlId) {
try {
const urlDoc = await getUrlDoc(urlId);
if (!urlDoc) throw new Error('Requested shortened url does note exist in kibana index');

updateMetadata(urlId, urlDoc);

return urlDoc._source.url;
} catch (err) {
return '/';
}
}
};
};
16 changes: 16 additions & 0 deletions src/ui/public/share/directives/share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const app = require('ui/modules').get('kibana');

app.directive('share', function () {
return {
restrict: 'E',
scope: {
objectType: '@',
objectId: '@',
setAllowEmbed: '&?allowEmbed'
},
template: require('ui/share/views/share.html'),
controller: function ($scope) {
$scope.allowEmbed = $scope.setAllowEmbed ? $scope.setAllowEmbed() : true;
}
};
});
76 changes: 76 additions & 0 deletions src/ui/public/share/directives/share_object_url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const app = require('ui/modules').get('kibana');
const Clipboard = require('clipboard');

require('../styles/index.less');

app.directive('shareObjectUrl', function (Private, Notifier) {
const urlShortener = Private(require('../lib/url_shortener'));

return {
restrict: 'E',
scope: {
getShareAsEmbed: '&shareAsEmbed'
},
template: require('ui/share/views/share_object_url.html'),
link: function ($scope, $el) {
const notify = new Notifier({
location: `Share ${$scope.$parent.objectType}`
});

$scope.textbox = $el.find('input.url')[0];
$scope.clipboardButton = $el.find('button.clipboard-button')[0];

const clipboard = new Clipboard($scope.clipboardButton, {
target(trigger) {
return $scope.textbox;
}
});

clipboard.on('success', e => {
notify.info('URL copied to clipboard.');
e.clearSelection();
});

clipboard.on('error', () => {
notify.info('URL selected. Press Ctrl+C to copy.');
});

$scope.$on('$destroy', () => {
clipboard.destroy();
});

$scope.clipboard = clipboard;
},
controller: function ($scope, $location) {
function updateUrl(url) {
$scope.url = url;

if ($scope.shareAsEmbed) {
$scope.formattedUrl = `<iframe src="${$scope.url}" height="600" width="800"></iframe>`;
} else {
$scope.formattedUrl = $scope.url;
}

$scope.shortGenerated = false;
}

$scope.shareAsEmbed = $scope.getShareAsEmbed();

$scope.generateShortUrl = function () {
if ($scope.shortGenerated) return;

urlShortener.shortenUrl($scope.url)
.then(shortUrl => {
updateUrl(shortUrl);
$scope.shortGenerated = true;
});
};

$scope.getUrl = function () {
return $location.absUrl();
};

$scope.$watch('getUrl()', updateUrl);
}
};
});
2 changes: 2 additions & 0 deletions src/ui/public/share/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require('./directives/share');
require('./directives/share_object_url');
Loading

0 comments on commit aad699f

Please sign in to comment.