Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid request scoped services #14482

Closed
wants to merge 11 commits into from
Closed
11 changes: 9 additions & 2 deletions example_plugins/baz/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import { BazService } from './BazService';
import { registerEndpoints } from './registerEndpoints';

export const plugin: KibanaPluginConfig<{}> = {
plugin: kibana => {
const { kibana: _kibana, elasticsearch, logger, util, http } = kibana;
// index patterns isn't needed in every place in kibana, so we can
// make it a separate dependency
depedencies: ['indexPatterns'],
plugin: (kibana, dependencies) => {
// uisettings and savedObjects seem like they're needed in a lot of
// places in kibana, so make them part of core
const { kibana: _kibana, elasticsearch, logger, util, http, UiSettingsService, SavedObjectsService } = kibana;

const { indexPatterns } = dependencies;

const log = logger.get();

Expand Down
14 changes: 14 additions & 0 deletions example_plugins/port_dashboards/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "port_dashboards",
"version": "1.0.0",
"scripts": {
"prepare": "npm run build",
"build": "tsc"
},
"devDependencies": {
"@elastic/kbn-types": "file:../../packages/kbn-types",
"example-plugin-bar": "file:../bar",
"typescript": "2.6.1"
}
}

30 changes: 30 additions & 0 deletions example_plugins/port_dashboards/src/PortDashboardsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { flatten } from 'lodash';
//TODO: pull this in
import { collectDashboards } from './collect_dashboards';

export class PortDashboardsService {
constructor(private readonly savedObjectsService) {
this.savedObjectsService = savedObjectsService;
}

async importDashboards(query, payload) {
const overwrite = 'force' in query && query.force !== false;
const exclude = flatten([query.exclude]);

const docs = payload.objects
.filter(item => !exclude.includes(item.type));

const objects = await this.savedObjectsService.bulkCreate(docs, { overwrite });
return { objects };
}

async exportDashboards(dashboards, config) {
const ids = _.flatten([dashboards]);

const objects = await collectDashboards(this.savedObjectsService, ids);
return {
version: config.get('pkg.version'),
objects,
};
}
}
30 changes: 30 additions & 0 deletions example_plugins/port_dashboards/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { KibanaPluginConfig } from '@elastic/kbn-types';
import { PortDashboardsService } from './PortDashboardsService';
import { registerEndpoints } from './registerEndpoints';

export const plugin: KibanaPluginConfig<{}> = {
plugin: (kibana, dependencies) => {
const {
kibana: _kibana,
elasticsearch,
logger,
util,
http,
config, // TODO: is config exposed?
SavedObjectsService,
} = kibana;

// example of creating a logger with a context
const log = logger.get('portDashboardsService');

log.info('creating portDashboards plugin');

const router = http.createAndRegisterRouter('/api/dashboards');

// what we pass into registerEndpoints
// is actually available in the endpoint def
// as well as the handler function for the
// route
registerEndpoints(router, logger, util.schema, config, SavedObjectsService, elasticsearch);
}
};
73 changes: 73 additions & 0 deletions example_plugins/port_dashboards/src/registerEndpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { exportDashboards } from '../../../lib/export/export_dashboards';
import { importDashboards } from '../../../lib/import/import_dashboards';
import { SavedObjectsService } from '../../../../../../../platform/saved_objects';
import Boom from 'boom';
import Joi from 'joi';
import moment from 'moment';
import { Router, LoggerFactory, Schema } from 'kbn-types';

export function registerEndpoints(
router: Router<PortDashboards>,
logger: LoggerFactory,
schema: Schema,
config,
SavedObjectsService,
elasticsearch,
) {
const log = logger.get('routes');

router.get(
{
path: '/api/kibana/dashboards/export',
validate: {
headers: object({}),
query: object({
dashboard: [string()] or string(),
}),
},
},
async (request, response) {
const { dashboard } = request.query;
const { headers } = request;
const currentDate = moment.utc();
const savedObjectsService = new SavedObjectsService(headers, elasticsearch);

return exportDashboards(dashboard, config(), savedObjectsService)
.then(resp => {
const json = JSON.stringify(resp, null, ' ');
const filename = `kibana-dashboards.${currentDate.format('YYYY-MM-DD-HH-mm-ss')}.json`;
reply(json)
.header('Content-Disposition', `attachment; filename="${filename}"`)
.header('Content-Type', 'application/json')
.header('Content-Length', json.length);
})
.catch(err => reply(Boom.boomify(err, { statusCode: 400 })));
}
);

router.post(
{
path: '/api/kibana/dashboards/import',
validate: {
headers: object({}),
payload: object({
objects: [object()],
version: string(),
}),
query: object({
force: boolean(), //TODO: default false
exclude: string() or [string()],
}),
},
},
async (request, response) {
const { query, payload } = request;
const { headers } = request;
const savedObjectsService = new SavedObjectsService(headers, elasticsearch);

return importDashboards(query, payload, savedObjectsService)
.then(resp => reply(resp))
.catch(err => reply(Boom.boomify(err, { statusCode: 400 })));
}
);
}
Empty file.
12 changes: 12 additions & 0 deletions example_plugins/short_url/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "short_url",
"version": "1.0.0",
"scripts": {
"prepare": "npm run build",
"build": "tsc"
},
"devDependencies": {
"@elastic/kbn-types": "file:../../packages/kbn-types",
"typescript": "2.6.1"
}
}
63 changes: 63 additions & 0 deletions example_plugins/short_url/src/ShortUrlService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import crypto from 'crypto';
import { get } from 'lodash';
import { shortUrlAssertValid } from './short_url_assert_valid';

import {
SavedObjectsService,
Logger,
} from '@elastic/kbn-types';

export class ShortUrlService {
constructor(
private readonly log: Logger,
private readonly savedObjectsService: SavedObjectsService,
) {
this.log = log;
this.savedObjectsService = savedObjectsService;
}

private async updateMetadata(doc) {
try {
await this.savedObjectsService.update('url', doc.id, {
accessDate: new Date(),
accessCount: get(doc, 'attributes.accessCount', 0) + 1
});
} catch (err) {
this.log('Warning: Error updating url metadata', err);
//swallow errors. It isn't critical if there is no update.
}
}

async generateUrlId(url) {
shortUrlAssertValid(url);

const id = crypto.createHash('md5').update(url).digest('hex');
const { isConflictError } = savedObjectsService.errors;

try {
const doc = await savedObjectsService.create('url', {
url,
accessCount: 0,
createDate: new Date(),
accessDate: new Date()
}, { id });

return doc.id;
} catch (error) {
if (isConflictError(error)) {
return id;
}

throw error;
}
}

async getUrl(id) {
const doc = await this.savedObjectsService.get('url', id);
updateMetadata(doc);

shortUrlAssertValid(doc.attributes.url);

return doc.attributes.url;
}
}
43 changes: 43 additions & 0 deletions example_plugins/short_url/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { KibanaPluginConfig } from '@elastic/kbn-types';
import { ShortUrlService } from './ShortUrlService';
import { registerEndpoints } from './registerEndpoints';

export const plugin: KibanaPluginConfig<{}> = {
// pretend like this plugin depends on indexPatterns:
// - indexPatterns isn't needed in every place in kibana
// so we can make it a separate dependency
// do this by declaring it in dependencies
dependencies: ['indexPatterns'],
plugin: (kibana, dependencies) => {
// this plugin depends on uiSettings and savedObjects:
// which seem like they're needed in a lot of
// places in kibana, so make them part of core
// here we pull them out of kibana
const {
kibana: _kibana,
elasticsearch,
logger,
util,
http,
UiSettingsService,
SavedObjectsService,
} = kibana;

// here we pull indexPatterns out of dependencies
const { indexPatterns } = dependencies;

// example of creating a logger with a context
const log = logger.get('shortUrlService');

log.info('creating ShortUrl plugin');

const router = http.createAndRegisterRouter('/api/shortUrl');

// what we pass into registerEndpoints
// is actually available in the endpoint def
// as well as the handler function for the
// route
registerEndpoints(router, logger, util.schema);
}
};

78 changes: 78 additions & 0 deletions example_plugins/short_url/src/registerEndpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Router, LoggerFactory, Schema } from 'kbn-types';
import { ShortUrlService } from './ShortUrlService';

export function registerEndpoints(
router: Router<ShortUrlService>,
logger: LoggerFactory,
schema: Schema,
config,
SavedObjectsService,
UiSettingsService,
elasticsearch,
) {
const { object, string, number, maybe } = schema;
const log = logger.get('routes');

router.get(
{
path: '/goto/:urlId',
validate: {
params: object({
urlId: string(),
}),
},
},
async (request, response) => {
log.info(`goto the url given the short id`);
const { urlId } = request.params;
try {
const savedObjectsService = new SavedObjectsService(request, elasticsearch);

const uiSettingsService = new UiSettingsService(request, savedObjectsService);

const shortUrlLookup = new ShortUrlService(server.log, savedObjectsService);

const url = await shortUrlLookup.getUrl(urlId);

const stateStoreInSessionStorage = await uiSettingsService.get('state:storeInSessionStorage');
if (!stateStoreInSessionStorage) {
response().redirect(config.get('server.basePath') + url);
return;
}

const app = kbnServer.uiExports.apps.byId.stateSessionStorageRedirect;
response.renderApp(app, {
redirectUrl: url,
});
} catch (err) {
response(handleShortUrlError(err));
}
}
);

router.post(
{
path: '/shorten',
validate: {
payload: object({
url: string(),
}),
},
},
async (request, response) => {
try {
const { url } = request.payload;

const savedObjectsService = new SavedObjectsService(request, elasticsearch);

const shortUrlLookup = new ShortUrlLookup(server.log, savedObjectsService);

const urlId = await shortUrlLookup.generateUrlId(url);
response(urlId);
} catch (err) {
response(handleShortUrlError(err));
}
}
);
}

10 changes: 10 additions & 0 deletions example_plugins/short_url/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "target/server"
},
"exclude": [
"node_modules"
]
}

Loading